React Hooks: useEffect Explained

ยท

7 min read

React Hooks: useEffect Explained

Introduction

The useEffect() hook provides a way to manage side effects in functional components, such as fetching data, modifying DOM, setting up timers and more.

In the article, we will explore the basics of the useEffect() hook and how it can be used to manage side effects in React projects.

What is the useEffect hook?

The useEffect() allows us to synchronize a component to an external system such as a server, database or browser API. It is commonly used for tasks that involve fetching data, modifying DOM, and setting timers.

Syntax:

useEffect(setup, dependencies)

useEffecct() takes two arguments:

  • setup function: A function where you will write the code that the Effect will run, and this function returns a cleanup function. The cleanup function is the place where we can remove the event listeners, clear timers, and disconnect from the servers.

  • Optional dependencies list: It contains all the variables and props that are referenced inside the setup function.

useEffect(
  // setup function
  () =>{

     // add evern listeners
     // set timers     

     // cleanup function
     return () => {
        // clear timers
        // remove event listeners
     }
   }
[] );  // dependency list

In React, hooks are called at the top level in your component, we can't call them inside loops, or conditional statements.

import {  useEffect } from 'react';

function Mycomponent() {

  // โœ…  It's right place to call Effect
   useEffect( () => {}, []); // That's right.  

    if(condtion)  {
       // ๐Ÿ”ด error: can't use Effect inside condition  
       useEffect( () => {}, []); // this is wrong!
    }

    for(){
       // ๐Ÿ”ด error: can't use Effect inside a loop
       useEffect( () => {}, []); // this is wrong!
    }

    // return JSX
    return <> ... </>
}

Understanding the dependencies list

The dependencies list is an important part of the Effect, as it determines when the Effect will run.

The followings are the three cases that can determine the execution of the Effect:

  1. Passing no list: If we don't pass a dependency list, the Effect will execute on every render.

     useEffect(() => {
       // This runs after every render
     });
    
  2. Passing an empty list: If we pass an empty list as the second argument, then the Effect will execute only oncemiss when the component is mounted.

     useEffect(() => {
       // This runs only on mount (when the component appears)
     }, []);
    
  3. Passing a list with variables: If we pass a list of variables that are referenced inside of the setup function, then the Effect will execute whenever any variable in the list changes.

     useEffect(() => {
       // This runs on mount *and also* if either v1 or v2 have changed since the last render
     }, [v1, v2]);
    

When we pass a list of dependencies to the Effect, React will compare the current dependency values to the previous values, and only re-run the Effect if any of the values have changed.

It is important to note that values in the dependency list should be primitive values like numbers, strings and booleans.

There is a problem with non-primitive values like objects, arrays and functions. On every render, objects get a new memory address. So If we pass an object in the dependency list, React will take it as a new value and run the Effect. In every render, This can make the Effect run more often than necessary and impact performance.

The Effect runs twice in Development

In development, if strict mode is on, React intentionally runs the Effect two times to help the developer spot bugs and ensure the Effects are only doing what they are supposed to do.

Here is an example, that shows that a component mount, unmount and then mount again in development mode.

React runs the setup when the component mounts and runs cleanup when the component unmounts.

In the following example, we can see that React first runs the setup function, then the cleanup function and again the setup function.

In this example, when the component is first time mounted, the Effect runs the setup function and logs the "Connect to server" message. And when the component is unmounted, the cleanup function is called with the message "Disconnect to server". Finally, React mounts the component again, and the Effect runs the setup function and logs the message "ConnectConnec to server".

Common use cases for useEffect hook

The Effect can be used in a variety of scenarios where we need to synchronize our component with an external system. Some of the most common use cases for the Effect include:

  • Fetching data:

    When we need to fetch data from an API or a database, we can use the Effect to perform a network request and update the component state with the retrieved data.

    Let's say we have a component that fetches a post from an API and displays it on a page. We can use the Effect to fetch the post when the component mounts and update the state with the fetched post.

      import { useState, useEffect } from 'react';
    
      function MyComponent() {
        const [post, setPost] = useState({});
    
        useEffect(() => {
          fetch('https://jsonplaceholder.typicode.com/posts/1')
            .then(response => response.json())
            .then(post => setData(post));
        }, []);
    
        return (
          <div>
            <h2>{post?.title}</h2>
            <p>{post?.body}</p>
          </div>
        );
      }
    

    In the above example, the useEffect() is used to fetch post data from an API when the component mounts. The empty dependency list [] ensures that the Effect runs only once when the component mounts. The fetched post data is then stored in the post state variable using the setPost function. The component then renders the fetched post data.

  • Setting up event listeners:

    Another common use case for the useEffect is to add and remove event listeners.

      import React, { useEffect } from 'react';
    
      function App() {
        useEffect(() => {
          // add an resize event listener
          window.addEventListener('resize', handleResize);
    
          // clean-up function
          return () => {
            // remove resize event listener
            window.removeEventListener('resize', handleResize);
          };
        });
    
        const handleResize = () => {
          console.log('Window was resized!');
        };
    
        return (
          <div>
            <h1>Event Listener Example</h1>
            <p>Open the console and resize the window to see the event listener in action.</p>
          </div>
        );
      }
    
      export default App;
    

    In the above example, we are using useEffect to add an event listener to the window object for the resize event. We're also returning a clean-up function that removes the event listeners when the component unmounts. This ensures that we don't have any memory leaks or unnecessary event listeners still running after the component is no longer being used.

  • Cleaning up the resources:

    Let's say we have a component that sets up a setInterval() function to update the state every second. When the component unmounts, we want to clear the interval, so that it doesn't continue to run in the background. Here is how we can do this with the useEffect:

      import React, { useState, useEffect } from 'react';
    
      const MyComponent = () => {
        const [count, setCount] = useState(0);
    
        useEffect(() => {
          const intervalId = setInterval(() => {
            setCount((prevCount) => prevCount + 1);
          }, 1000);
    
          // Return a cleanup function to clear the interval when the component unmounts
          return () => clearInterval(intervalId);
        }, []);
    
        return <div>Count: {count}</div>;
      };
    
      export default MyComponent;
    

    In this example, we use the useEffect to set up the interval and return a clean-up function that clears the interval when the component unmounts. The clean-up function only runs once when the component unmounts.

Best practices for using the useEffect hook

Here are some of the best practices that we should keep in mind while using Effect hook:

  • Declare all dependencies: Make sure to declare all dependencies in the dependency list of the Effect. This will ensure that the Effect is only re-run when dependencies change, and prevent unnecessary re-renders.

  • Use the return statement: If the Effect creates any resources (e.g. event listeners, timers, or subscriptions), make sure to clean them up when the component unmounts. This can be done in the clean-up function that is returned from the Effect hook.

  • Use multiple useEffect hooks: Instead of using one useEffect to handle all side effects, use multiple useEffect to separate concerns and improve readability.

Conclusion

In conclusion, a useEffect() hook allows React developers to synchronize their components with external systems and manage side effects. It offers a simple way to perform various operations, such as fetching data, setting up event listeners, and cleaning up resources in an efficient manner.

When using useEffect, it is important to follow the best practices to make sure the code is maintainable and optimized. This includes carefully selecting the dependency array and cleaning up resources when it is necessary.


References

ย