Skip to content
react

How to solve Infinity loop in React's useEffect

Apr 1, 2023Abhishek EH5 Min Read
How to solve Infinity loop in React's useEffect

Have you started using the useEffect hook recently and encountered the following error?

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

This error might be tricky to fix. In this article, we will see different scenarios in which the above error can occur and will see how to fix the infinity loop caused by useEffect.

No dependency array

Consider the below code:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const [counter, setCounter] = useState(0)
5 useEffect(() => {
6 setCounter(value => value + 1)
7 })
8
9 return <div className="App">{counter}</div>
10}
11
12export default App

In the above code, we are calling setCounter inside the useEffect hook and the counter increments. As the state changes, the component gets re-rendered and useEffect runs again and the loop continues.

The useEffect hook runs again as we did not pass any dependency array to it and causes an infinite loop.

To fix this, we can pass an empty array [] as a dependency to the useEffect hook:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const [counter, setCounter] = useState(0)
5 useEffect(() => {
6 setCounter(value => value + 1)
7 }, [])
8
9 return <div className="App">{counter}</div>
10}
11
12export default App

Now if you run the app, the useEffect will be called only once in production and twice in development mode.

Objects as dependencies

Consider the following code:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const person = {
5 name: "John",
6 age: 23,
7 }
8
9 const [counter, setCounter] = useState(0)
10 useEffect(() => {
11 setCounter(value => value + 1)
12 }, [person])
13
14 return <div className="App">{counter}</div>
15}
16
17export default App

In the above code, we are passing the person object in the dependency array and the values within the object do not change from one render to another. Still. we end up in an infinite loop. You might wonder why.

The reason is, that each time the component re-renders, a new object is created. The useEffect hook checks if the 2 objects are equal. As 2 objects are not equal in JavaScript, even though they contain the same properties and values, the useEffect runs again causing infinite loop.

If you closely observe, you will see the following warning given by the ESLint in VSCode:

The 'person' object makes the dependencies of useEffect Hook (at line 12) change on every render. Move it inside the useEffect callback. Alternatively, wrap the initialization of 'person' in its own useMemo() Hook.eslintreact-hooks/exhaustive-deps

To fix it, we can wrap the object declaration inside a useMemo hook. When we declare an object inside a useMemo hook, it does not get recreated in each render unless the dependencies change.

App.js
1import { useEffect, useMemo, useState } from "react"
2
3function App() {
4 const person = useMemo(
5 () => ({
6 name: "John",
7 age: 23,
8 }),
9 []
10 )
11
12 const [counter, setCounter] = useState(0)
13 useEffect(() => {
14 setCounter(value => value + 1)
15 }, [person])
16
17 return <div className="App">{counter}</div>
18}
19
20export default App

Alternatively, you can also specify the individual properties in the dependency array as [person.name, person.age].

Functions as dependencies

The below code will also cause an infinite loop:

App.js
1import { useEffect, useState } from "react"
2
3function App() {
4 const getData = () => {
5 // fetch data
6 return { foo: "bar" }
7 }
8 const [counter, setCounter] = useState(0)
9 useEffect(() => {
10 const data = getData()
11 setCounter(value => value + 1)
12 }, [getData])
13
14 return <div className="App">{counter}</div>
15}
16
17export default App

Here also the ESLint will warn us with the following message:

The 'getData' function makes the dependencies of useEffect Hook (at line 12) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'getData' in its own useCallback() Hook.eslintreact-hooks/exhaustive-deps

Like how it happened for objects, even function gets declared each time causing an infinite loop.

We can fix the infinite loop by wrapping the function inside useCallback hook, which will not re-declare the function until the dependencies change.

App.js
1import { useCallback, useEffect, useState } from "react"
2
3function App() {
4 const getData = useCallback(() => {
5 // fetch data
6 return { foo: "bar" }
7 }, [])
8 const [counter, setCounter] = useState(0)
9 useEffect(() => {
10 const data = getData()
11 setCounter(value => value + 1)
12 }, [getData])
13
14 return <div className="App">{counter}</div>
15}
16
17export default App

Conditions inside useEffect

Consider a scenario where you want to fetch the data and update the state as shown below:

App.js
1import { useEffect, useState } from "react"
2
3function Child({ data, setData }) {
4 useEffect(() => {
5 // Fetch data
6 setData({ foo: "bar" })
7 }, [data, setData])
8
9 return <div className="App">{JSON.stringify(data)}</div>
10}
11
12export const App = () => {
13 const [data, setData] = useState()
14 return <Child data={data} setData={setData} />
15}
16
17export default App

Here as well, you will end up in an infinite loop. You could prevent it by putting a condition inside the useEffect to check if data does not exist. If it doesn't, then only fetch the data and update the state:

App.js
1import { useEffect, useState } from "react"
2
3function Child({ data, setData }) {
4 useEffect(() => {
5 if (!data) {
6 // fetch data
7 setData({ foo: "bar" })
8 }
9 }, [data, setData])
10
11 return <div className="App">{JSON.stringify(data)}</div>
12}
13
14export const App = () => {
15 const [data, setData] = useState()
16 return <Child data={data} setData={setData} />
17}
18
19export default App

Do follow me on twitter where I post developer insights more often!

Leave a Comment

© 2024 CodingDeft.Com