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

avatar
Borislav Hadzhiev

Last updated: Apr 6, 2024
7 min

banner

# 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

# Immediately calling an event handler

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

pass function to onclick event handler

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.

# Setting the state in the body of a function component

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 and updates the state, which causes a re-render and does that infinitely.

# Use the useEffect hook to only set the state once

One way to solve the error is to call the setState function in a useEffect hook.

index.js
import {useState, useEffect} from 'react'; export default function App() { const [counter, setCounter] = useState(); useEffect(() => { setCounter(100); }, []); return ( <div> <h1>Count: {counter}</h1> </div> ); }

use useeffect hook to only set state once

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.

# Pass an initial value to the useState() hook

You could also 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.

I've written a detailed guide on setting a conditional initial value for useState.

# Use a condition or an event handler to update the state

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.

# Solving the error when using the useEffect hook

The 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 get the warning "React Hook useEffect has a missing dependency", check out the following article.

# Looking for a condition that prevents infinite re-renders

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 some logic determines whether the state should be updated, and the state shouldn't be set on every re-render.

# Don't use objects or arrays that change as useEffect dependencies

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.

# Moving the object inside of the useEffect hook

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

# Passing the properties of the object to the dependency array

An alternative solution is to pass the 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.

# Using the useMemo hook to memoize the object or array

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.

# Arrays are also compared by reference

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.

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

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.