Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks error: Rendered more hooks than during the previous render

I used to have a function component act as a page:

export default function NormalList(props) {
    const pageSize = 20;
    const [page, setPage] = useState(1)
    const [searchString, setSearchString] = useState(null);
    const [creditNotes, setCreditNotes] = useState(() => getCreditNoteList());
    const [ordering, setOrdering] = useState(null);

    useEffect(() => getCreditNoteList(), [page, searchString, ordering]);

    function getCreditNoteList() {
        API.fetchCreditNoteList({
            account_id: props.customerId, page, page_size: pageSize, searchString, ordering
        }).then(data => {
            setCreditNotes(data);
        });
    }
    return (<>{creditNotes.results.map(record => <...>}</>)
}

And this has been running fine, but recently I need to wrap around NormalList with ListPage component:

export default function ListPage(props) {
    const customerId = props.match.params.customer_id;
    return (<div>...<div><NormalList/></div></div>)
}

Then all the sudden I am getting this error Rendered more hooks than during the previous render.

It seems to me that setCreditNotes(data) inside getCreditNoteList is causing the error, but I don't know why.

like image 244
James Lin Avatar asked Feb 11 '20 02:02

James Lin


People also ask

Does useEffect runs after every render?

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.

Should I always use React Hooks?

Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That's what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

Are React Hooks callbacks?

The React useCallback Hook returns a memoized callback function. Think of memoization as caching a value so that it does not need to be recalculated. This allows us to isolate resource intensive functions so that they will not automatically run on every render.

Why React Hooks are use full?

Hooks make React so much better because you have simpler code that implements similar functionalities faster and more effectively. You can also implement React state and lifecycle methods without writing classes.

How to fix “rendered more hooks than during the last render”?

If we conditionally call a hook or return before all of the hooks have finished, we get the error “Rendered more hooks than during the last render”. Move all hooks to the top level of the function component and avoid using hooks within conditions to fix the problem. Uncaught Invariant Violation: Rendered more hooks than during the previous render.

How to deal with “react hooks are not working” error?

If you’ve ever written React with hooks, chances are, you’ve probably been greeted with this error at one time. O ne of the things you might want to do when such error occurs is to ensure you are not violating any the rules from the Hooks documentation. Call Hooks from React function components. Don’t call Hooks from inside a function or a loop.

Is it okay to change the number of hooks an app returns?

But if you do it as a component, then it’s perfectly fine to change how many components App returns, and when those components render, they’ll only call one hook each. // ...

How to solve the error when using hooks inside conditions?

To solve the error, move all hooks at the top level of the function component and don't use hooks inside conditions. Here is an example of how the error occurs. The issue is that we are conditionally calling the useEffect hook.


2 Answers

So there are a couple of things you need to fix. First of all you should remove your function call from the useState function, you should only perform your side effects inside a useEffect hook see React Docs.

The next thing is whenever you decide to use the dependency array to your useEffect hook you should include all the dependencies of useEffect i.e all the props, state including functions inside your function component that you used inside your useEffect hook. So the Rule of Thumb is Never Lie About Your Dependencies! otherwise you will shoot yourself in the foot.

So the easiest option is to move your getCreditNoteList function inside your useEffect hook and add all the dependencies of your useEffect hook to the dependency array.


export default function NormalList({ customerId }) {
    const pageSize = 20;
    const [page, setPage] = useState(1)
    const [searchString, setSearchString] = useState(null);
    const [creditNotes, setCreditNotes] = useState({});
    const [ordering, setOrdering] = useState(null);

    useEffect(() => {

      function getCreditNoteList() {
        API.fetchCreditNoteList({
            account_id: customerId, 
            page, 
            page_size: pageSize, 
            searchString, 
            ordering
        }).then(data => {
            setCreditNotes(data);
        });
      }

      getCreditNoteList(),

       // add ALL! dependencies
    }, [page, searchString, ordering, pageSize, customerId]))

    return (
       <> </>
    )
}

Second Option If you want to use the getCreditNoteList function elsewhere and want to keep it outside your useEffect hook you can do that by wrapping your getCreditNoteList logic inside the useCallback hook as shown below and add the function to your dependency array inside your useEffect hook for reasons i mentioned earlier.

export default function NormalList({ customerId }) {
    const pageSize = 20;
    const [page, setPage] = useState(1)
    const [searchString, setSearchString] = useState(null);
    const [creditNotes, setCreditNotes] = useState({});
    const [ordering, setOrdering] = useState(null);

    const getCreditNoteList = useCallback(() => {
        API.fetchCreditNoteList({
            account_id: customerId, 
            page, 
            page_size: pageSize, 
            searchString, 
            ordering
        }).then(data => {
            setCreditNotes(data);
        });
     // the function only changes when any of these dependencies change
    },[page, searchString, ordering, pageSize, customerId])

    useEffect(() => {

      getCreditNoteList(),

    },[getCreditNoteList])

    return (
       <> </>
    )
}
like image 171
Chitova263 Avatar answered Oct 17 '22 15:10

Chitova263


OK My problem was that I had 2 issues with importing the elements, first being pycharm cleverly automatically imported them wrong, and secondly being I didn't import one of the component at all.

I wish the error message could be a bit more specific than "Rendered more hooks than during the previous render".

Also I tested I didn't need to move function body getCreditNoteList inside useEffect.

Thanks to @chitova263 for spending the time to help out.

like image 34
James Lin Avatar answered Oct 17 '22 15:10

James Lin