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

13

151

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.

Hyperesthesia answered 10/4, 2019 at 23:45 Comment(0)
A
64

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?

Alpers answered 10/4, 2019 at 23:59 Comment(1)
Right, bad mistake on my part. I get () => function() and function() mixed up in onClicks all the time.Hyperesthesia
G
126

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>)
}
Greatgranduncle answered 4/1, 2021 at 5:51 Comment(2)
Yes, this is a great solution for simple components but sometimes you just want to not run the hooks if something is undefined.Euratom
Link to documentation: Only call Hooks at the top level (before any early returns). This question reactjs - Why Call React Hooks at the Top Level? - Stack Overflow explains the rationale behind the requirement.Colloquy
A
64

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?

Alpers answered 10/4, 2019 at 23:59 Comment(1)
Right, bad mistake on my part. I get () => function() and function() mixed up in onClicks all the time.Hyperesthesia
D
24

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

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

and it will work perfectly

Detection answered 11/4, 2019 at 8:31 Comment(0)
B
12

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
Briney answered 17/8, 2020 at 9:55 Comment(3)
This also solved my problem. Can someone explain why this causes an error?Suppository
we can assume, <Comp /> is a jsx and is understood to be a react component, while {Comp(..} although doing same, is not interpreted as react component, and thus does not extra optimizationsBriney
The explanation can be found in React Functional component: calling as function vs. as component , specifically the first blog post I linked in the comment.Colloquy
R
11

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
}
Rosy answered 24/10, 2020 at 12:50 Comment(0)
N
9

One possible reason for this error is that use

Component() // error
<Component /> // good

here is the explanation:

If you call the component function directly, it's just a function that returns JSX (no state, no effects). The hook inside does not work properly, it may be considered a hook of a parent component. If you use <Component />, it is equivalent to executing React.createElement, and the hook inside will work as expected.

Neodymium answered 12/9, 2023 at 11:41 Comment(0)
P
2

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>
    }
}
Punishment answered 3/7, 2022 at 18:49 Comment(0)
B
2

If you get this error on a really big project with the particular component having so much code:

 ERROR  Error: Rendered more hooks than during the previous render.

It is likely due to the fact that the new hook you are trying to call, for example a useEffect hook, that was not there before, is unreachable.

Try moving the hook up a bit as high as possible in the code and this should solve the issue. If not, please refer to the docs and make sure you are properly using React hooks.

Bibliofilm answered 19/1 at 3:17 Comment(0)
I
0
import React from 'react'

const AddEmployee = () => {

 return (
 <>AddEmployee</>
 )
 }

export default AddEmployee



import { Link, useNavigate } from 'react-router-dom';
const ListEmployeeComponent = () => {
const navigator1 = useNavigate(); 
//below all the normal code like useEffect....
// and
 function addNewEmp() 
 {
  navigator1('/AddEmployee') //defined a path in routes
 }


   return (
   <button onClick={addNewEmp}>Add Employee</button>
   JSX)

Note : if you add the const navigator1 = useNavigate(); below useEffect and all you will get an error Uncaught Error: Rendered more hooks than during the previous render. and your page will not render for me this worked please try this and let me know

Internationalism answered 3/1 at 3:0 Comment(0)
S
0

You cannot have an early return before a useEffect hook

for example

if(!userId) return
useEffect(() => {
  //logic 
})

if the condition by which you are returning early is required in the useEffect logic move it inside otherwise make sure that check happens after all useEffects

useEffect(() => {
if(!userId) return
  //logic 
})

// alternatively

 useEffect(() => {
  //logic 
})
if(!userId) return
Sublunary answered 11/3 at 20:0 Comment(0)
E
-1

I think this could be classified as a bug, but curious to get the developers' thoughts on this. Basically, the Rendered more hooks than during the previous render error does not seem to get thrown when the transition is from 0 hooks to a positive number of hooks.

The error does get thrown when, for example, a component goes from rendering 1 hook to 2 hooks.

React version: 17.0.2

Ellingston answered 20/2, 2023 at 9:41 Comment(3)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Britt
Text copied from github.com/facebook/react/issues/23182Protist
Sorry, we are no longer accepting answers from your account because most of your answers need improvement or do not sufficiently answer the question. See the Help Center to learn more.Ellingston
M
-2

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) }
Mongolism answered 30/10, 2020 at 9:52 Comment(0)
L
-3

You have to use your hooks before return in a components

Larock answered 27/6, 2022 at 4:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.