All you need to know about React Hooks: useEffect

All you need to know about React Hooks: useEffect

ยท

8 min read

This is the second post in the React Hooks series. Throughout this series, we will cover every React hook in detail and discuss its implementation. Moreover, we'll explore practical examples, best practices, and dos and don'ts. By the end of this series, you'll have a comprehensive understanding of React hooks and how to leverage them effectively in your projects. Also, do check out the first post on the useState hook where we learn everything about useState.

Introduction to useEffect hook

What is useEffect hook?

useEffect is one of the most used hooks in React after useState. The useEffect hook is used to handle side-effects (side-effecting functions are those functions that affect something outside of their scope. For example, fetching data from an API, updating a global state or manipulating the DOM, etc).

useEffect takes two parameters setup function and dependency array:-

  • setup function - This is where the logic goes that you want to execute in the useEffect. This will run whenever your component mounts in the DOM.

  • Cleanup function(optional) - The setup function can optionally return a cleanup function that will run first after every time a re-render occurs(any changes in the value of the dependency array triggers a re-render). React will first run the cleanup function with the old values, and then run your setup function with the new values, and after your component unmount, React will run the cleanup function to clean the effect.

  • Dependencies(optional) - In this array, we pass all those values that are used inside our useEffect hook be it props, state or any variables and functions. Whenever the value of any dependency changes, react will compare the new value to the old value using the Object.is comparison and if there is a change in value, it will trigger a re-render and re-run of the effect.

useEffect returns undefined.

Basic Syntax and usage of the useEffect hook

Before using Hooks in your code you need to import them from React.

import { useEffect } from 'react'
// useEffect hook syntax
useEffect(() => {
    // setup code
    return () => {
        // cleanup code
    };
}, [dependecy1, dependency2]) // Dependency array

useEffect(function, [dependecies]), setupfunction(code to be executed inside useEffect) and dependecy array.

Fetching Data with useEffect

In the below example, we're using a test API jsonplaceholder to fetch data from inside useEffect hook.

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

const ExampleComponent = () => {
  const [data, setData] = useState({});

  useEffect(() => {
  // Effect function to be executed after rendering
   const fetchData = async () => {
      try {
            const response = 
                 await fetch('https://jsonplaceholder.typicode.com/todos');
            const data = await response.json();
            setData(data);
          } catch (error) {
            console.error('Error fetching data:', error);
          }
    };
    fetchData(); // Fetching data from an API
  }, []); // Empty dependency array means the effect runs only once after initial rendering

  return (
    <div>
      {data ? (
        <ul>
          {data.map((item) => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      ) : (
        <p>Loading data...</p>
      )}
    </div>
  );
};

export default ExampleComponent;

In the above example -

  • The useEffect hook is used to fetch data from an API after the component has been rendered.

  • Since an empty dependency array ([]) is passed as the second argument, the effect runs only once, after the initial rendering of the component.

  • The fetchData function uses the fetch API to retrieve data from the specified URL. Upon successful response, the data is stored in the component's state using the setData function otherwise it'll print the error message.

  • The rendered JSX displays a list of items if the data is available, or a loading message if the data is being fetched.

Cleanup function

Why should we use the useEffect cleanup function?

Let's assume we above we have the same above component that fetches and renders data from an API. If our component unmounts before our promise resolves, useEffect will try to update the state (on an unmounted component) and throws an error like below:

Warning Error from useEffect without cleanup function

To fix this issue, we must use the cleanup function.

React will perform cleanup when the component unmounts and on every re-render to clean up the previous render before running the effect again.

As the above issue suggests, the cleanup function is usually used to cancel all the subscriptions and async tasks like fetch requests.

Clean up the subscription

useEffect(() => {
    // set our variable to true
    let isApiSubscribed = true;
    fetch(API)
         .then((response) => {
         if (isApiSubscribed) {
             // handle success
         }
    });
    return () => {
        // cancel the subscription
        isApiSubscribed = false;
    };
}, []);

In the above code, we set the variable isApiSubscribed to true and then use it in our success request. in the cleanup function, we're changing the variable isApiSubscribed to false, when we unmount our component.

Dependency Array

What is Dependency Array?

The second parameter that the useEffect takes as input is a dependency array, every reactive value that is used inside the Effect must be specified inside the array.

useEffect will re-run every time the value of any variable inside the dependency array changes.

Common use case of dependency array

function component() {
    const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/todos'); // reactive value
    const [data, setData] = useState({}); // this too a reactive value

    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch(url); // here we're reading a reactive value (url)
            const resData = await response.json();
            setData(resData);
        }
        fetchData();
        return () => {
            setData({});
        }
    }, [url]) // โœ… if we're using any reactive value inside useEffect we must specify them
}

Reactive values - This includes props, variables, states and functions declared inside a component that is bound to change.

The above code Effect will run after the initial render and whenever the values of the dependency array, url changes.

In the above example, the code will first run when the component mounts and after that whenever the url changes, it'll first run the cleanup function and then the setup function.

Note:-

๐Ÿšฉ You can't ignore the dependencies used inside your Effect. Every value that is used inside Effect's code must be declared as a dependency.

Pitfalls and mistakes

  • Effect running after every re-render

    If you remove the dependency array and did not pass even an empty array, then your Effect will run after every single render (and re-render). It will run on every change that will occur in the component.

useEffect(() => {
  // ...
}); // ๐Ÿšฉ No dependency array: re-runs after every render!
  • Effect running in an infinite loop

    It can be because of the following two things-

    - Your Effect is updating some states.

    - That state triggers a re-render, and the state's value is passed in the dependency array, which causes the Effect's dependencies to change.

Let's take an example from above, where we're fetching data from API and setting the state.

function component() {
    const [url, setUrl] = useState('https://jsonplaceholder.typicode.com/todos');
    const [data, setData] = useState({});

    useEffect(() => {
        const fetchData = async () => {
            const response = await fetch(url); 
            const resData = await response.json();
            setData(resData);
        }
        fetchData();
        return () => {
            setData({});
        }
    }, [url, data]) // ๐Ÿšฉ here we're passing a reactive value that is changing inside the Effect. This will trigger an infinite loop of re-renders.
}

In the above example, we're passing a reactive value inside the dependency array, while we're changing the same reactive value inside the same Effect. As we know every time value of any dependency variable changes, it'll trigger a re-render.

Image shows the infinite loop problem that can be trigger due to changing the dependency value from inside the useEffect.

  • useEffect running twice

    When Strict Mode is on, in development, React runs setup and cleanup one extra time before the actual setup. This is development-only behaviour to stress-test and find any issue in the logic. It will be ignored in the production.

  • useEffect causes a flicker in the UI

    As we know useEffect runs at last after the component finishes painting UI. If your Effect doing DOM manipulation, it might be caused because it tries to change the initial UI on the initial render and you see a little flicker.

    To deal with this issue, you need to block the initial DOM paint, by replacing useEffect with useLayoutEffect. This is not required most of the time, you'll only need this if you want to run your Effect before the browser paint.

References

  1. React.dev(preferred) - Official updated React documentation.

  2. LogRocket blog - Complete guide to useEffect Hook.

  3. useEffect cleanup function - All about cleanup function.

tl;dr

This post covers the useEffect hook in React, which is used to handle side-effects such as fetching data from an API, updating a global state, or manipulating the DOM. It explains how to use the useEffect hook, how to use the useEffect cleanup function, and how to use the useLayoutEffect hook to block the initial DOM paint. It also provides references to the official React documentation, a LogRocket blog, and a useEffect cleanup function.

Thanks for reading the article till the end! If you got something useful from this, give a follow, it'll be a whole series of blogs covering every React Hook in detail and more. Do like and comment if you have any questions.

The next article will be on useContext hook.

Did you find this article valuable?

Support Aryan Srivastava by becoming a sponsor. Any amount is appreciated!

ย