Too many re-renders. React limits the number of renders to prevent an infinite loop

avatar

Borislav Hadzhiev

Tue Apr 05 20226 min read

banner

Photo by Hiva Sharifi

Too many re-renders. React limits the number of renders to prevent an infinite loop #

The error "Too many re-renders. React limits the number of renders to prevent an infinite loop" occurs for multiple reasons:

  1. Calling a function that sets the state in the render method of a component.
  2. Immediately invoking an event handler, instead of passing a function.
  3. Having a useEffect hook that sets and re-renders infinitely.

too many re renders react limits the number

Here is an example of how the error occurs:

App.js
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 here is that we are immediately calling the setCounter function in the onClick event handler.

The function is invoked immediately on page load and not when the event is triggered.

To solve the error, we have to pass a function to the onClick event handler and not the result of calling one.

App.js
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, which sets the state and re-renders the component infinitely.

The error also occurs if we try to set the state immediately in a component, without using a condition or an event handler.

App.js
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 in the code snippet above is that the setCounter function gets invoked when the component renders, updates the state, which causes a re-render and does that infinitely.

You could solve the error by passing an initial value or a function to the useState() hook to initialize the state.

App.js
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 be 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.

Alternatively, you can use a condition or an event handler like in the previous examples.

App.js
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> ); }
If you use a condition like in the example above, make sure that the condition doesn't always return a truthy value, which would cause an infinite re-render loop.

The "Too many re-renders. React limits the number of renders to prevent an infinite loop" error also occurs when using the useEffect method with dependencies that cause infinite re-renders.

App.js
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 in the code snippet above is that we haven't passed a dependency array to the useEffect hook, which means the hook is ran on every render, it updates the component's state, then is ran again infinitely.

One way to solve the error is to provide an empty array as the second argument to useEffect.

App.js
import {useEffect, useState} from 'react'; export default function App() { const [counter, setCounter] = useState(0); useEffect(() => { setCounter(counter + 1); }, []); // 👈️ empty dependency array return ( <div> <h1>Count: {counter}</h1> </div> ); }

If you pass an empty dependency array as the second argument to the useEffect method, the method is only ran on the initial render of the component.

The code above increments the counter to 1 and never runs again, regardless whether the App component is re-rendered or not.

If you have to specify a dependency that re-renders your component infinitely, try to look for a condition that prevents this.

App.js
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> ); }

Chances are there is some logic that determines whether the state should be updated, and the state shouldn't be set on every re-render.

Make sure you aren't using an object or array that is different on every render as a dependency to the useEffect hook.

App.js
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 in the code snippet above 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.

One way to solve the error is to move the object inside of the useEffect hook, so we can remove it from the dependencies array.

App.js
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 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 properties of the object to the dependency array.

App.js
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.

Alternatively, we can use the useMemo hook to get a memoized value that doesn't change between renders.

App.js
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> ); }
We wrapped the initialization of the object inside of the useMemo hook to get a memoized value that doesn't change between renders.

The second parameter we passed to the useMemo hook is a dependency array which determines when the callback function we passed to it as the first parameter 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 amount of times.

App.js
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 the 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.

App.js
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.

Use the search field on my Home Page to filter through my more than 1,000 articles.