Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 918
timothym Avatar asked Apr 10 '19 23:04

timothym


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.

Do hooks cause Rerender?

Every state change in a hook, whether it affects its return value or not, will cause the “host” component to re-render.

Which React hooks can cause a 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).

Are React Hooks called every render?

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.


2 Answers

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?

like image 102
Jake Worth Avatar answered Oct 21 '22 13:10

Jake Worth


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>) } 
like image 27
Rahul Shah Avatar answered Oct 21 '22 12:10

Rahul Shah