How to solve Infinity loop in React's useEffect?
Tags: react
Published: 2022-11-24 13:46:45

Have you started using the [useEffect](https://www.codingdeft.com/posts/react-useeffect-hook/) 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:

```jsx
import { useEffect, useState } from "react"

function App() {
  const [counter, setCounter] = useState(0)
  useEffect(() => {
    setCounter(value => value + 1)
  })

  return <div className="App">{counter}</div>
}

export 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:

```jsx
import { useEffect, useState } from "react"

function App() {
  const [counter, setCounter] = useState(0)
  useEffect(() => {
    setCounter(value => value + 1)
  }, [])

  return <div className="App">{counter}</div>
}

export default App
```

Now if you run the app, the useEffect will be called only once in production and [twice in development](https://www.codingdeft.com/posts/react-use-effect-running-twice/) mode.

## Objects as dependencies

Consider the following code:

```jsx
import { useEffect, useState } from "react"

function App() {
  const person = {
    name: "John",
    age: 23,
  }

  const [counter, setCounter] = useState(0)
  useEffect(() => {
    setCounter(value => value + 1)
  }, [person])

  return <div className="App">{counter}</div>
}

export 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness), 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.

```jsx
import { useEffect, useMemo, useState } from "react"

function App() {
  const person = useMemo(
    () => ({
      name: "John",
      age: 23,
    }),
    []
  )

  const [counter, setCounter] = useState(0)
  useEffect(() => {
    setCounter(value => value + 1)
  }, [person])

  return <div className="App">{counter}</div>
}

export 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:

```jsx
import { useEffect, useState } from "react"

function App() {
  const getData = () => {
    // fetch data
    return { foo: "bar" }
  }
  const [counter, setCounter] = useState(0)
  useEffect(() => {
    const data = getData()
    setCounter(value => value + 1)
  }, [getData])

  return <div className="App">{counter}</div>
}

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

```js
import { useCallback, useEffect, useState } from "react"

function App() {
  const getData = useCallback(() => {
    // fetch data
    return { foo: "bar" }
  }, [])
  const [counter, setCounter] = useState(0)
  useEffect(() => {
    const data = getData()
    setCounter(value => value + 1)
  }, [getData])

  return <div className="App">{counter}</div>
}

export default App
```

## Conditions inside useEffect

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

```jsx
import { useEffect, useState } from "react"

function Child({ data, setData }) {
  useEffect(() => {
    // Fetch data
    setData({ foo: "bar" })
  }, [data, setData])

  return <div className="App">{JSON.stringify(data)}</div>
}

export const App = () => {
  const [data, setData] = useState()
  return <Child data={data} setData={setData} />
}

export 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:

```jsx
import { useEffect, useState } from "react"

function Child({ data, setData }) {
  useEffect(() => {
    if (!data) {
      // fetch data
      setData({ foo: "bar" })
    }
  }, [data, setData])

  return <div className="App">{JSON.stringify(data)}</div>
}

export const App = () => {
  const [data, setData] = useState()
  return <Child data={data} setData={setData} />
}

export default App
```