Last updated: Apr 6, 2024
Reading time·7 min
The error "Too many re-renders. React limits the number of renders to prevent an infinite loop" occurs for multiple reasons:
useEffect
hook that sets and re-renders infinitely.Here is an example of how the error occurs:
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. return ( <div> <button onClick={setCounter(counter + 1)}>Increment</button> <h1>Count: {counter}</h1> </div> ); }
The issue is that we are immediately calling the setCounter
function in the
onClick
event handler.
To solve the error,
pass a function to the onClick
event handler,
and not the result of calling one.
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); return ( <div> <button onClick={() => setCounter(counter + 1)}> Increment </button> <h1>Count: {counter}</h1> </div> ); }
Now we are passing a function to the event handler and not calling the
setCounter
method on page load.
setState
action is triggered and the component re-renders infinitely.The error also occurs if we try to set a component's state immediately, without using a condition or an event handler.
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setCounter(counter + 1); return ( <div> <h1>Count: {counter}</h1> </div> ); }
The issue is that the setCounter
function gets invoked when the component
renders and updates the state, which causes a re-render and does that
infinitely.
useEffect
hook to only set the state onceOne way to solve the error is to call the setState
function in a useEffect
hook.
import {useState, useEffect} from 'react'; export default function App() { const [counter, setCounter] = useState(); useEffect(() => { setCounter(100); }, []); return ( <div> <h1>Count: {counter}</h1> </div> ); }
Notice that we used an empty array for the hook's dependencies.
The useEffect
hook is only run after the component mounts, so the state is
only set once.
useState()
hookYou could also solve the error by passing an initial value or a function to the useState() hook to initialize the state.
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(() => 100 + 100); return ( <div> <h1>Count: {counter}</h1> </div> ); }
We passed a function to the useState
method. The function will only get
invoked the first time the component renders and will calculate the initial
state. You could also pass an initial value directly to the useState
method.
I've written a detailed guide on setting a conditional initial value for useState.
Alternatively, you can use a condition or an event handler like in the previous examples.
import {useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); // 👇️ Your condition here if (Math.random() > 0.5) { setCounter(counter + 1); } return ( <div> <h1>Count: {counter}</h1> </div> ); }
truthy
value as this would cause an infinite re-render loop.useEffect
hookThe error also occurs when using the useEffect method with dependencies that cause infinite re-renders.
import {useEffect, useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); useEffect(() => { // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setCounter(counter + 1); }); // 👈️ Forgot to pass dependency array return ( <div> <h1>Count: {counter}</h1> </div> ); }
The issue is that we haven't passed a dependencies array to the useEffect
hook.
This means that the hook is run on every render, it updates the component's state and then is run again infinitely.
One way to solve the error is to provide an empty array as the second argument
to useEffect
.
import {useEffect, useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); useEffect(() => { setCounter(counter + 1); }, []); // 👈️ Empty dependencies array return ( <div> <h1>Count: {counter}</h1> </div> ); }
useEffect
method, the method is only run on the initial render of the component.The code increments the counter to 1
and never runs again, regardless of
whether the App
component is re-rendered or not.
If you get the warning "React Hook useEffect has a missing dependency", check out the following article.
If you have to specify a dependency that re-renders your component infinitely, try to look for a condition that prevents this.
import {useEffect, useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); useEffect(() => { // 👇️ Some condition here if (Math.random() > 0.5) { setCounter(counter + 1); } }, [counter]); return ( <div> <h1>Count: {counter}</h1> </div> ); }
useEffect
dependenciesMake sure you aren't using an object or array that is different on every render
as a dependency to the useEffect
hook.
import {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); const obj = {country: 'Chile', city: 'Santiago'}; useEffect(() => { // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setAddress(obj); console.log('useEffect called'); }, [obj]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
The issue is that objects are compared by reference in JavaScript. The obj
variable stores an object with the same key-value pairs, but different reference
(different location in memory) on every render.
useEffect
hookOne way to solve the error is to move the object inside of the useEffect
hook,
so we can remove it from the dependencies array.
import {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); useEffect(() => { // 👇️ Move object inside of `useEffect` // and remove it from the dependencies array const obj = {country: 'Chile', city: 'Santiago'}; setAddress(obj); console.log('useEffect called'); }, []); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
An alternative solution is to pass the properties of the object to the dependency array.
import {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); const obj = {country: 'Chile', city: 'Santiago'}; useEffect(() => { setAddress({country: obj.country, city: obj.city}); console.log('useEffect called'); // 👇️ Object properties instead of the object itself }, [obj.country, obj.city]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
Now React isn't testing whether an object changed, it is testing whether the
obj.country
and obj.city
strings have changed between renders.
useMemo
hook to memoize the object or arrayAlternatively, we can use the useMemo hook to get a memoized value that doesn't change between renders.
import {useEffect, useMemo, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); // 👇️ Get memoized value const obj = useMemo(() => { return {country: 'Chile', city: 'Santiago'}; }, []); useEffect(() => { setAddress(obj); console.log('useEffect called'); }, [obj]); return ( <div> <h1>Country: {address.country}</h1> <h1>City: {address.city}</h1> </div> ); }
useMemo
hook to get a memoized value that doesn't change between renders.The second argument we passed to the useMemo
hook is a dependencies array
which determines when the callback function we passed to useMemo
is re-run.
Note that arrays are also compared by reference in JavaScript. So an array with
the same values could also cause your useEffect
hook to be triggered an
infinite number of times.
import {useEffect, useMemo, useState} from 'react'; export default function App() { const [nums, setNums] = useState([1, 2, 3]); const arr = [4, 5, 6]; useEffect(() => { // ⛔️ Too many re-renders. React limits the number // of renders to prevent an infinite loop. setNums(arr); console.log('useEffect called'); }, [arr]); return <div>{nums[0]}</div>; }
The array stores the same values between re-renders but points to a different location in memory and has a different reference each time the component is re-rendered.
The same approaches we used for objects are valid when working with arrays. For
example, we can use the useMemo
hook to get a memoized value that doesn't
change between renders.
import {useEffect, useMemo, useState} from 'react'; export default function App() { const [nums, setNums] = useState([1, 2, 3]); const arr = useMemo(() => { return [4, 5, 6]; }, []); useEffect(() => { setNums(arr); console.log('useEffect called'); }, [arr]); return <div>{nums[0]}</div>; }
We wrapped the initialization of the array inside of the useMemo
hook to get a
memoized value that doesn't change between renders.
You can learn more about the related topics by checking out the following tutorials: