What is the useEffect() Hook in React?


React hooks are relatively new in the seasoned developer’s world. This brings with it some complexities as they are a re-design of the solutions to the same old existing problems.

In the case of the useEffect hook clarity and understandability are among the complexities faced by most programmers. In this blog, I will expound on this hook more clearly, its applications as well as how to use it effectively.


Introduction

The useEffect hook allows you to synchronize a functional component with an external system or in simpler terms the ‘outside world’.

Our functional components in some instances may be required to reach beyond their scope after rendering and perform actions in the outside world, for example when making an API call. These instances are referred to as side effects and are quite unpredictable, making them hard to manage.

The useEffect allows you to manage these side effects in a declarative and predictable way in our functional components. Other examples of side effects include:

  • Manually changing the DOM

  • When using a timer; setTimeout and setInterval

  • Subscribing to events

It is however advisable to know when it is appropriate to use the useEffect hook. The idea of using it is to execute code that needs to happen during the lifecycle of the component instead of on specific user interactions or DOM events. This means it should not be applied in place of event handlers.


Syntax and Usage

Similar to other React hooks to use the useEffect hook, you should first import it from React:

import { useEffect } from 'react';

useEffect takes 2 arguments:

  • The effect function: This is the function containing the code that handles the side effect. By default, It will be executed after every render of the component.

  • The dependency array (optional): As the name suggests it contains values (props or state variables) which the effect function depends on. If the values change the effect is re-run again even though rendering is complete. When this array is omitted, the effect will re-run every time the component is rendered. When the array is empty the effect runs only once after initial rendering.

useEffect(() => {
        //function to be executed
},[]) //empty dependency array

Let’s put it all together in a simple example:

  • Create a new functional component, PostList :
import React, { useState, useEffect } from 'react';

const PostList = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // Fetch data from the JSONPlaceholder API
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => setPosts(data));
  }, []); // Empty dependency array to run the effect only once

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.body}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

The PostList component contains one state variable ‘posts’ whose value is updated using useEffect which fetches a list of posts from the JSONPlaceholder API.

The data is then mapped and displayed in an unordered list using the map function.

JSONPlaceholder is a fake online REST API for testing and prototyping. It provides a variety of data, including posts, users, comments, and more.

  • The component needs to be attached to the root component App:
import React from 'react';
const App = () => { return ( <div className="App"> <PostList /> </div> ); }
export default App;

Output

A screen snip of the codes output

In this instance, useEffect fetches data from an external API and passes it on to be loaded to the page.


Cleaning up

Recall that, the useEffect hook is often used for tasks such as setting up subscriptions, timers, or modifying the DOM, it’s important to properly clean up after these tasks to avoid:

  1. memory leaks

  2. unnecessary processing

  3. or to save on resources.

You can think of it as a way of putting an end to side effects that don’t need to be executed anymore.

For instance, say a componentOne makes an API request for some data, but while making this asynchronous request, componentOne is removed from the DOM (unmounted). There is no need to complete the asynchronous request hence a cleanup method can be added to cleanup the asynchronous request from completing.

A common indicator that your useEffect may require a cleanup function is the error:

Warning: Can't perform a React state update on an unmounted component. 
This is a no-op, but it indicates a memory leak in your application. 
To fix, cancel all subscriptions and asynchronous tasks 
in a useEffect cleanup function.

useEffect is built in such a way that we can return a function inside it and this return function is where the cleanup happens.

An example of how we can clean up an API request on unmount as described for ‘componentOne’, could work as follows:

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

const componentOne = () => {  
const [data, setData] = useState(null);  
const [error, setError] = useState(null);  

useEffect(() => {  
// Create a controller to cancel the fetch request  
 const controller = new AbortController();  
 const signal = controller.signal;  

// Fetch data from an API  
 fetch("https://example.com/api/data", { signal })  
  .then((response) => response.json())  
  .then((data) => setData(data))  
  .catch((error) => setError(error));  

// Return a function that cancels the fetch request  
 return () => {  
 controller.abort();  
};  
}, []); // Run only once when the component mounts  

// Render the data or error  
if (error) {  
 return <div>Error: {error.message}</div>;  
 } else if (data) {  
 return <div>Data: {JSON.stringify(data)}</div>;  
 } else {  
 return <div>Loading...</div>;  
 }  
}import React, { useState, useEffect } from “react”;

When ‘componentOne’ unmounts and the fetch request has not been completed, the request is canceled.

This is just one example of side effects that need to be cleaned up in React. There are multiple others, whose clean-up functions vary depending on the specific implementation being undertaken in the useEffect.


Conclusion

The useEffect hook elevates the efficiency and interactivity of our apps by allowing us to better manage side effects. An understanding of it thus guarantees we can handle the challenges of asynchronous programming and craft applications that seamlessly marry functionality with user experience. Happy Coding!