timothym
timothym

Reputation: 3275

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.

Upvotes: 173

Views: 338452

Answers (14)

Mujahidul Islam
Mujahidul Islam

Reputation: 563

🔴 Do not call Hooks after a conditional return statement.

Upvotes: 6

Gabriel
Gabriel

Reputation: 51

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

Upvotes: 3

Bryan
Bryan

Reputation: 71

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.

Upvotes: 5

Ashfaq
Ashfaq

Reputation: 29

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

Upvotes: 0

MisakaMikoto
MisakaMikoto

Reputation: 201

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.

Upvotes: 20

thien
thien

Reputation: 11

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

Upvotes: 0

KARTHIKEYAN.A
KARTHIKEYAN.A

Reputation: 20158

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

Upvotes: 2

arpan das
arpan das

Reputation: 36

You have to use your hooks before return in a components

Upvotes: -3

Rahul Shah
Rahul Shah

Reputation: 2043

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

Upvotes: 157

imshashi17
imshashi17

Reputation: 197

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
}

Upvotes: 11

Aadil Mehraj
Aadil Mehraj

Reputation: 2614

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

Upvotes: -2

Akshay Vijay Jain
Akshay Vijay Jain

Reputation: 16025

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

Upvotes: 13

Ertan Hasani
Ertan Hasani

Reputation: 1773

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

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

and it will work perfectly

Upvotes: 24

Jake Worth
Jake Worth

Reputation: 5852

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?

Upvotes: 64

Related Questions