DEV Community

Cover image for Mastering React's useEffect Hook: A Comprehensive Guide
Fatima Olasunkanmi-Ojo
Fatima Olasunkanmi-Ojo

Posted on • Updated on

Mastering React's useEffect Hook: A Comprehensive Guide

Introduction of useEffect Hook

The useEffect hook is a powerful tool that was introduced in React 16.8 as a part of the Hooks API. It allows developers to perform side effects, such as updating the DOM, fetching data, and subscribing to events in function components. Prior to useEffect, class components were used to handle such side effects, but with Hooks, developers can achieve the same functionality without having to write a class. The useEffect hook is versatile and can be used for a wide range of tasks, making it a popular and important feature in React development.

In this article, you will learn about the useEffect hook and how you can use it in your React Application. You will use a function component because React Hooks don't work inside classes.

Prerequisites

Understanding this article requires the following:

  • Installation of Node.js
  • Basic knowledge of JavaScript and React

Get Started

Use yarn create vite <project-name> to create a new React project. Vite is used because it is fast in development and build times of React applications.

Start the application by running the following command:

cd project-name
yarn run dev
Enter fullscreen mode Exit fullscreen mode

UseEffect Hook syntax and default behaviour

useEffect is a function which accepts two parameters, a callback function and a dependency array. To create a side effect with useEffect hook in your project, import the hook inside the functional component to access the states and props without writing additional code.
useEffect by default runs after every render. It runs both after the first render and also after every update.
Here is an example of how to declare a useEffect hook in a functional component:

// App.js

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

const App =()=> {
  const [count, setCount] = useState(0)

  useEffect(()=>{
    console.log(count);
  })

  return (
    <div style={{textAlign: "center", marginTop: "90px"}}> 
      <button onClick={() => setCount(prevCount => prevCount + 1)}>Increment Counter       </button>
      <h1>This is {count} </h1>
    </div>
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

The code snippet above does the following:

  • Imports useEffect and useState from React
  • Creates a state variable with its corresponding set function and initiates the state with 0 as a value
  • Add a button with a click handler to increment the count by passing in a function to update the previous state whenever the button is clicked
  • Call the useEffect function within the component and pass in the function, which is executed after every render of the components

Preventing infinite loops in useEffect Hook

useEffect runs after every render and can cause an infinite loop when it renders, which in some cases, could lead to performance issues. To prevent this, you need to conditionally run the useEffect Hook from a functional component and provide the second parameter called "the dependency array" when you call the useEffect function.
The dependency array usually contains values that, if the value changes from one render to the next, it will cause the useEffect to run. This helps limit the number of times the effect runs and determines when it will run instead of running after every render.

// App.js
... 
  useEffect(()=>{
    console.log(count);
  }, [])
...
Enter fullscreen mode Exit fullscreen mode

In the code above, the useEffect function contains an array as its second parameter, called a dependency array.

An empty dependency array denotes that the function will run first for once when the components load, and there is no dependency to watch and trigger the effect to run again.

You can run the useEffect function every time count changes by adding count to the dependency array, as shown below:

// App.js
... 
  useEffect(()=>{
    console.log(count);
  }, [count])
...
Enter fullscreen mode Exit fullscreen mode

In the code above, when the component runs for the first time, the state starts at an initial value of 0, and anywhere the count is called, the value is replaced with 0, including the dependency array. Clicking the Increment Counter button manually triggers a re-render of the component and updates the state count from 0 to 1, and all the count values are updated to 1 wherever it is used in the function

In the code snippet below, you will fetch meme images from the meme API and display a new image whenever a button clicks. You can use useEffect to fetch data from a server and display it without problems. Once the component is rendered, it will fetch and render the data.

// Meme.js

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

const Meme = () => {
  const [memeData, setMemeData] = useState({randomImage: "http://i.imgflip.com/1bij.jpg"});
  const [allMemeImages, setAllMemeImages] = useState([]);

  useEffect(() => {
    fetch("https://api.imgflip.com/get_memes")
      .then((res) => res.json())
      .then((data) => setAllMemeImages(data.data.memes));
  }, []);

  const handleClick = () => {
    const randomNum = Math.floor(Math.random() * allMemeImages.length);
    const url = allMemeImages[randomNum].url;
    setMemeData((prevState) => {
      return {
        ...prevState,
        randomImage: url,
      };
    });
  };

  return (
      <div style={{ textAlign: "center", marginTop: "90px" }}>
        <button onClick={handleClick}>Get a new meme image</button>
        <div style={{ textAlign: "center", marginTop: "90px" }}>
          <img
            src={memeData.randomImage}
            alt='meme-images'
            style={{ width: "300px", height: "300px" }}
          />
        </div>
      </div>
  );
};
export default Meme;
Enter fullscreen mode Exit fullscreen mode

The code above does the following:

  • Import useEffect and useState from React
  • Declares the initial state for metadata to display an image on the first render of the component with setMemeData as the set function
  • Declares the initial state for allMemeImages to store all the images from the meme API endpoint
  • useEffect hook is called, and the fetch function is used to make a call to the meme API and store all the data using the setAllMemeImages. An empty dependency array indicates that the meme API is called once with the useEffect hook
  • Declares handleClick function to get a new image when the button is clicked. This function randomly generates a URL from the list of all the image URL gotten from the meme API and stored in the allMemeImages state. In this function, setMemeData is used to re-render the component on every click, which makes the image change to another image on the screen

To display the application on the browser, import the Meme.js file inside the App.js file, as shown below.

//App.js

import React from 'react'
import Meme from './Meme.js';
const App =()=> {
  return (
   <Meme />
  )
}
export default App;
Enter fullscreen mode Exit fullscreen mode

At this point, your application should look like the following:

final result

Conclusion

This article discusses useEffect hook and how you can use it in your React Application to call API endpoints. It explained the default behaviour of useEffect and how you can prevent infinite loops while using useEffect hook.
Follow the resources below to learn more about useEffect hooks.

Resources

Top comments (5)

Collapse
 
mattbarnicle profile image
Matt Barnicle

Great example for beginners. I didn't get the concept of useEffect right away. This will help people get acquainted with the hook.

FYI, it looks like you've got a typo with setMemeDataetadata. Probably a search and replace artifact :)

Collapse
 
fatimaola profile image
Fatima Olasunkanmi-Ojo

Yes it will help beginners understand better

Thank you for pointing out the typo. I will fix it

Collapse
 
thethirdrace profile image
TheThirdRace

Excellent start for people beginning in React.

I would like to point out a few misconceptions about useEffect so we can all better apply this hook.

useEffect by default runs after every render. It runs both after the first render and also after every update.

useEffect runs after every render and can cause an infinite loop when it renders, which in some cases, could lead to performance issues. To prevent this, you need to conditionally run the useEffect Hook from a functional component and provide the second parameter called "the dependency array" when you call the useEffect function.

An empty dependency array denotes that the function will run first for once when the components load

The render is a synchronous process and should always be pure. There shouldn't be any asynchronous code in the render process.

useEffect fills the asynchronous role, it will run after every render. It allows us to synchronize the state with "external" systems and dispatch actions automatically (meaning actions that shouldn't be dispatched by the user).

Because useEffect is asynchronous, there are no concept of mount or update like there used to be with Class Components. useEffect simply runs after every render without exception.

My last sentence touches the biggest misconception about useEffect, the dependency array is not used to decide if useEffect is ran or not, it's used by useEffect to decide if the callback function we passed to it will be run or not.

It's a very important nuance, but it applies to all hooks the same way. Every single hook is executed without exception, the dependency array is only used to decide if the hook should call the callback function or not.

As for the infinite loop possibility, the dependency array will do nothing about it. See this example:

const MyComponent = ()=> {
  const [count, setCount] = useState(0)

  useEffect(()=>{
    setCount(count + 1)
  }, [count, setCount])

  return (<div>{count}</div>)
}
Enter fullscreen mode Exit fullscreen mode

The infinite loop happens when we don't understand the relationship between the render and useEffect:

State change => render => asynchronous useEffect

In the example, the component goes from no state to an initial state. This is a state change, which triggers the first render. Then useEffect is executed because count has changed from no state to 0. The callback changes the count to 1, which is a state change which will trigger a render. The render triggers useEffect since count is now 1 and not 0 anymore, which triggers a state change, which triggers a state change, etc.

At no point the dependency array solved the problem here. What solves the problem is understanding how the piece fits together and taking the necessary precaution to stop the infinite loop.

I hope it clears up the model a bit 😊

Collapse
 
mwendwabundi profile image
Mwendwa Bundi Emma

You explained it so well. Thank you

Collapse
 
fatimaola profile image
Fatima Olasunkanmi-Ojo

You are welcome