Handling Side Effects in React with useEffect

By Maulik Paghdal

19 Dec, 2024

Handling Side Effects in React with useEffect

Introduction

React’s functional components brought a new way of managing state and side effects with hooks. One of the most powerful hooks is useEffect, designed specifically to handle side effects like data fetching, DOM manipulations, or subscriptions in a clean and declarative way.

In this blog, we’ll explore the workings of useEffect, common scenarios where it is used, and best practices to ensure efficient and bug-free React applications.

What is useEffect ?

useEffect is a React hook that lets you perform side effects in functional components. Side effects can include operations such as:

  • Fetching data from an API.
  • Interacting with the DOM (e.g., adding event listeners).
  • Setting up timers or subscriptions.

The hook provides a way to execute code after the component renders or updates, mimicking lifecycle methods in class components like componentDidMount, componentDidUpdate, and componentWillUnmount.

Syntax of useEffect

useEffect(() => {
  // Your side effect logic here.
  return () => {
    // Optional cleanup function.
  };
}, [dependencies]);

Key Points:

  1. Effect function: The first argument contains the side effect logic.
  2. Cleanup function: An optional return function for cleaning up resources.
  3. Dependency array: Specifies dependencies that trigger the effect when changed.

Common Use Cases for useEffect

1. Fetching Data from an API

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

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

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/posts")
      .then((response) => response.json())
      .then((data) => setData(data));
  }, []); // Empty array ensures this runs only once after the initial render.

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

export default DataFetcher;
  • Explanation: The API call runs once after the component mounts. The empty dependency array ([]) ensures the effect doesn’t rerun unnecessarily.

2. Listening to DOM Events

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

const MouseTracker = () => {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const updatePosition = (event) => {
      setPosition({ x: event.clientX, y: event.clientY });
    };

    window.addEventListener("mousemove", updatePosition);

    return () => {
      window.removeEventListener("mousemove", updatePosition); // Cleanup event listener
    };
  }, []); // Runs only once

  return (
    <div>
      <h2>Mouse Position</h2>
      <p>
        X: {position.x}, Y: {position.y}
      </p>
    </div>
  );
};

export default MouseTracker;
  • Explanation: This example demonstrates setting up and cleaning up an event listener to track mouse movement.

3. Running Effects When Dependencies Change

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

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

  useEffect(() => {
    console.log(`Count updated to: ${count}`);
  }, [count]); // Effect runs whenever 'count' changes

  return (
    <div>
      <h2>Counter</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default Counter;
  • Explanation: The effect runs every time the count state changes.

4. Cleanup Function in useEffect

import React, { useEffect } from "react";

const Timer = () => {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log("Timer is running...");
    }, 1000);

    return () => {
      clearInterval(timer); // Cleanup the interval when the component unmounts
    };
  }, []);

  return <h2>Check the console for timer logs!</h2>;
};

export default Timer;
  • Explanation: The cleanup function clears the interval to prevent memory leaks when the component unmounts.

Best Practices for Using useEffect

  1. Use Cleanup Functions: Always clean up subscriptions, timers, or event listeners in the cleanup function to prevent memory leaks.
  2. Minimize Dependencies: Only include necessary dependencies in the dependency array to avoid unnecessary rerenders.
  3. Avoid Infinite Loops: Ensure dependencies are correctly managed to prevent effects from running in a loop.
  4. Separate Concerns: Split effects into multiple useEffect hooks if they serve different purposes.

Conclusion

The useEffect hook is a cornerstone of React's functional component architecture, enabling developers to handle side effects cleanly and efficiently. By understanding its syntax, use cases, and best practices, you can leverage useEffect to build robust and scalable React applications.

Experiment with the examples shared in this blog, and master the art of managing side effects in React!