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.
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.
Every state change in a hook, whether it affects its return value or not, will cause the “host” component to re-render.
As we already saw before, React re-renders a component when you call the setState function to change the state (or the provided function from the useState hook in function components).
Yes they are called on each render, in the first render it initialise a memory cell, on re-render it read the value of the current cell : When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one.
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?
I faced the same issue. What I was doing was something like this:
const Table = (listings) => { const {isLoading} = useSelector(state => state.tableReducer); if(isLoading){ return <h1>Loading...</h1> } useEffect(() => { console.log("Run something") }, []) return (<table>{listings}</table>) }
I think what was happening was that on the first render, the component returned early and the useEffect didn't run. When the isLoading state changed, the useEffect ran and I got the error - the hook rendered more times than the previous render.
A simple change fixed it:
const Table = (listings) => { const {isLoading} = useSelector(state => state.tableReducer); useEffect(() => { console.log("Run something") }, []) if(isLoading){ return <h1>Loading...</h1> } return (<table>{listings}</table>) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With