Max RozenMax Rozen
TwitterArticlesNewsletter

How to prevent infinite re-renders when using useEffect

Let’s take a step back, pause for a moment, and think about what useEffect actually does.

By default, useEffect always runs after render has run. This means if you don’t include a dependency array, and you’re using useEffect to fetch data to display it, you will always trigger another render after useEffect runs.

Unless you provide useEffect a dependency array.

The dependency array

The dependency array in useEffect lets you specify the conditions to trigger it. If you provide useEffect an empty dependency array, it’ll run exactly once, as in this example (CodeSandbox link):

import React, { useEffect, useState } from 'react';

export default function DataDisplayer() {
  const [data, setData] = useState('');

  useEffect(() => {
    const getData = async () => {
      const response = await fetch(`https://swapi.dev/api/people/1/`);
      const newData = await response.json();
      setData(newData);
    };

    getData();
  }, []); //<-- This is the dependency array

  if (data) {
    return <div>{data.name}</div>;
  } else {
    return null;
  }
}

What if you wanted to let users decide which id they wanted to query, and forgot to add id to the dependency array? You’d cause an infinite loop.

export default function DataDisplayer(props) {
  const [data, setData] = useState('');

  useEffect(() => {
    const getData = async () => {
      const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);
      const newData = await response.json();
      setData(newData);
    };

    getData();
  }); //<-- Notice the missing dependency array

Why does the above example cause an infinite loop?

  1. Your first render runs, and because data is falsey, render returns null and kicks off useEffect
  2. useEffect runs, fetching your data, and updating it via setData
  3. Since data has been updated, the component re-renders to display the new data value
  4. However, useEffect runs after each render, so it runs again, updating data via setData
  5. Repeat steps 3 and 4 until your app crashes

Preventing infinite loops

This is where the dependency array comes in handy.

Adding props.id to it will ensure useEffect only runs if props.id changes:

useEffect(() => {
  const getData = async () => {
    const response = await fetch(`https://swapi.dev/api/people/${props.id}/`);
    const newData = await response.json();
    setData(newData);
  };
  getData();
}, [props.id]); //<-- This is the dependency array, with a variable

Do you struggle to keep up with best practices in React?

I send a single email weekly with an article like this one to help improve the quality of your React apps. Lots of developers like them, and I'd love to hear what you think as well. You can always unsubscribe.

    Join 43 React developers who signed up last week!

    rssTwitterGitHub

    © 2020 Max Rozen. All rights reserved.