Search code examples
javascriptreactjsreact-hooks

Uncaught Invariant Violation: Rendered more hooks than during the previous render


I have a component that looks like this (very simplified version):

const component = (props: PropTypes) => {

    const [allResultsVisible, setAllResultsVisible] = useState(false);

    const renderResults = () => {
        return (
            <section>
                <p onClick={ setAllResultsVisible(!allResultsVisible) }>
                    More results v
                </p>
                {
                    allResultsVisible &&
                        <section className="entity-block--hidden-results">
                            ...
                        </section>
                }
            </section>
        );
    };

    return <div>{ renderResults() }</div>;
}

When I load the page this component is used on, I get this error: Uncaught Invariant Violation: Rendered more hooks than during the previous render. I tried to find an explanation of this error, but my searching returned no results.

When I modify the component slightly:

const component = (props: PropTypes) => {

    const [allResultsVisible, setAllResultsVisible] = useState(false);

    const handleToggle = () => {
        setAllResultsVisible(!allResultsVisible);
    }

    const renderResults = () => {
        return (
            <section>
                <p onClick={ handleToggle }>
                    More results v
                </p>
                {
                    allResultsVisible &&
                        <section className="entity-block--hidden-results">
                            ...
                        </section>
                }
            </section>
        );
    };

    return <div>{ renderResults() }</div>;
}

I no longer get that error. Is it because I included the setState function within the jsx that is returned by renderResults? It would be great to have an explanation of why the fix works.


Solution

  • The fix works because the first code sample (the erroring one) invokes a function inside onClick, while the second (the working one) passes a function to onClick. The difference is those all-important parentheses, which in JavaScript mean 'invoke this code'.

    Think of it this way: in the first code sample, every time component is rendered, renderResults is invoked. Every time that happens, setAllResultsVisible(!allResultsVisible), rather than waiting for a click, is called. Since React performs the render on its own schedule, there's no telling how many times that will happen.

    From the React docs:

    With JSX you pass a function as the event handler, rather than a string.

    React Handling Events Docs

    Note: I wasn't able to get this exact error message when running the first code sample in a sandbox. My error referred to an infinite loop. Maybe a more recent version of React produces the error described?