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

avatar

Borislav Hadzhiev

Last updated: Jul 25, 2022

banner

Photo from Unsplash

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 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, 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.

If the method is called on page load, a 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.

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

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 as this 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 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.

App.js
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> ); }
If you pass an empty dependencies array as the second argument to the 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 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 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 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.

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

Conclusion #

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.
I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.