简体   繁体   中英

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.

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>)
}

You can simply change your onlick event add () => before setAllResultsVisible

<p onClick={() => setAllResultsVisible(!allResultsVisible) }> 
    More results v
</p>

and it will work perfectly

Even after the fixes above, there are a few other causes as well for this error. I am writing below one use case which occurred for me.

function Comp(props){return <div>{props.val}</div>}

This component can be called in the following ways in jsx:

1. <Comp val={3} /> // works well
2. { Comp({val:3}) } // throws uncaught invariant violation error, at least it throw in my case, may be there were other factors, but changing back to first way removed that problem

See the question can be React :

  1. Rendered lesser hooks than the previous render.
  2. Rendered more hooks than the previous render.

In both the cases thing can be like you have a conditional statement calling the same function which returns render from different places like both wrapped in a parent return function:

const parentFunc = () => {
    if(case==1)
        return function_a();
    if (case==2)
        return function_b();
}

now function_a() could be a function creating two or one hook suppose useStyle() or anything else

and function_b() could be a function creating no hook.

Now, when parentFunc returns function_a() rendering one hook and function_b() rendering no hook then react will tell you that from the same render function two different renders were returned one with two or one hook and the other with one hook this disparity leads to the error. Error being

less hooks were rendered. And the error is quite obvious.

When cases are reversed and function_b() is returned first cause of the conditionals then react will tell you that from the same render function different renders were returned and error will be .

Rendered more hooks than previous render.

Now, Solution:

Change the code flow like maybe create function_ab() which will ensure all the hooks being used are rendered and in that function:

const function_ab = () => {
    if(case==1)
         return (<div></div>) //or whatever
    if(case==2)
         return (<div>I am 2 </div>) //or whatever
}

In my case I have used setState() hook inside if condition in the following way, so I got an error after that I have resolved. According to react hook document we should not use hooks inside if condition.

Error:

import React, { useState, useCallback } from 'react';
import './style.css';

export default function App() {
  const [count, setCount] = useState(0);
if(count < 10){
  return (
    <div>
      <h1>Hello Count!</h1>
        <button onClick={useCallback(setCount((count) => count + 1))}>
          click to add
        </button>
    </div>
  );
} else {
        return <div>Count reached 10!</div>
    }
 }

Solution:

import React, { useState, useCallback } from 'react';
import './style.css';

export default function App() {
  const [count, setCount] = useState(0);
  const handleIncrement = useCallback(() => {
        setCount((count) => count + 1)
  })
  
if(count < 10){
return (
    <div>
      <h1>Hello Count!</h1>
      <button onClick={handleIncrement}>click to add</button>
    </div>
  );
} else {
        return <div>Count reached 10!</div>
}

The issue is within the onClick as the setAllResultsVisible is called, it will trigger state change and result on every render

onClick={ setAllResultsVisible(!allResultsVisible) }

Change this to function call instead:

onClick={_ => setAllResultsVisible(!allResultsVisible) }

You have to use your hooks before return in a components

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM