Why does React's Effect Hook ( useEffect ) Cause an Infinite Loop?

Why does React's Effect Hook ( useEffect ) Cause an Infinite Loop?

While writing a new functional React component, you used an Effect Hook (useEffect(() => { fetch some data})) to fetch some API data your components need to display. And now your component is stuck in an infinite loop that continuously fetches the data without end. There should be no reason why the component is stuck in an endless loop, so why is it happening??

The React Documentation states that

The Effect Hook lets you perform side effects in function components:

Isn't an API call exactly the kind of side effect that the useEffect hook is designed to do?

The answer is yes, and I've setup an example of this infinite loop behavior here.

infinite useEffect example

The example contains two cards, both of which are components that should only call a timeout function (simulating an expensive fetch request) inside a useEffect hook when their colors change, and only when their colors change.  The card on the left runs the timeout function continuously while the card on the right only does so when the change color button is clicked on.

Rendering order of React's useEffect Hook

Let's take a closer look at the two cards. First the one on the left:

export default function ListItemInfty({ color, val }) {
  const [expOp, setExpOp] = useState(0);

  useEffect(() => {
    setTimeout(() => { setExpOp(expOp + 1) }, 1000);

Since we are using functional react component, the entire component function is called every time one of its props changes. Additionally, the entire function is also called whenever one of its state props changes.

By default, whenever a component rerenders, the useEffect hook is called.

So what happens when the side-effect of the useEffect hook includes a state update?

You guessed it... the entire component renders again, calling the useEffect hook... which updates our state... to infinity, and beyond!

Use Effect Hook's Dependency Argument to Manage State Updates

The right card uses useEffect's optional second argument, the hook's dependency argument, to make sure that the hook is only called when the component's color property changes.

export default function ListItemDepColor({ color, val }) {
  const [expOp, setExpOp] = useState(0);

  useEffect(() => {
    setTimeout(() => { setExpOp(e => e + 1) }, 1000);
  }, [color]);

What does this tell us? It means we should always keep the hook's intended side-effects in mind while using it.

If the component is intended to display some user data, then the effect should only be called when the use-id props changes. This minimizes the number of rerenders on the component, speeding your app up.

And always, always pass in a dependency argument whenever the useEffect hook makes a component state update.

Want to modify the examples for your use case? You can find the code used in the above example at my React-Examples Repo, with the full code for the left card and the right card.

Happy Coding!

Show Comments