DEV Community

Cover image for React's useEffect Hook Simplified: Manage Side Effects Like a Pro
chintanonweb
chintanonweb

Posted on

React's useEffect Hook Simplified: Manage Side Effects Like a Pro

Understanding useEffect in React: From Zero to Hero

React has become one of the most popular JavaScript libraries for building dynamic user interfaces. One of the most crucial hooks in React is useEffect, which allows developers to manage side effects in functional components. Side effects include operations like fetching data, setting up subscriptions, or manually manipulating the DOM. In this blog, we will dive deep into what useEffect is, how it works, and provide step-by-step examples for better understanding.

What Is useEffect?

In React, useEffect is a built-in hook that allows you to perform side effects in function components. Side effects, as the name suggests, are operations that affect something outside of the function, such as API calls, timers, logging, or updating the DOM.

Before the introduction of hooks in React 16.8, you had to use class components and lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount to handle side effects. Now, with useEffect, these lifecycle events are combined into a single function for functional components.

Why Choose useEffect?

useEffect is a powerful hook for managing side effects in React for several reasons:

  1. Simplification of Code: It eliminates the need for class-based components and lifecycle methods, allowing you to write cleaner, functional-based code.
  2. Centralized Side Effects: You can manage all side effects, such as fetching data or updating the DOM, in a single place.
  3. Improved Readability: It streamlines how lifecycle events are managed, making the code more readable and less complex.
  4. Flexibility: With useEffect, you have more control over when and how often side effects are executed, as you can define dependencies that determine when the effect should run.

How Does It Work?

The useEffect hook accepts two arguments:

  1. Effect function: This function contains the side effect logic, like fetching data or setting up a subscription.
  2. Dependency array (optional): An array of values that determines when the effect should be re-run. If any value in the dependency array changes, the effect is executed again. If you omit this array, the effect will run after every render.

Here’s a basic structure:

useEffect(() => {
  // Side effect logic goes here

  return () => {
    // Optional cleanup function
  };
}, [/* Dependencies go here */]);
Enter fullscreen mode Exit fullscreen mode

Example:

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

function ExampleComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // Fetching data when the component mounts
    fetch('https://jsonplaceholder.typicode.com/posts/1')
      .then((response) => response.json())
      .then((json) => setData(json));

    // Optional cleanup (in this case, not needed)
    return () => {
      // Cleanup logic if necessary
    };
  }, []); // Empty array means this effect will only run once when the component mounts

  return <div>{data ? data.title : 'Loading...'}</div>;
}
Enter fullscreen mode Exit fullscreen mode

In this example, the data is fetched from an API when the component is first rendered, and the result is displayed in the UI. Since we pass an empty dependency array, this effect runs only once after the first render.

Controlling Side Effects in useEffect

By controlling when useEffect runs, we can optimize performance and ensure that the side effects occur at the correct time.

Effects Without Cleanup

Not all effects require cleanup. Cleanup is only necessary when you need to remove or reset something after the effect is executed, such as clearing timers or unsubscribing from data streams.

For example, here’s a scenario where no cleanup is needed:

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

function NoCleanupEffect() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Effect without cleanup runs every time the count changes');
  }, [count]); // Runs every time `count` changes

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In this case, the effect runs every time the count state changes. Since we’re not setting up subscriptions or managing external resources, no cleanup is necessary.

Effects with Cleanup

If your effect involves setting up subscriptions or timers, you’ll likely need to clean up after the effect. For example, imagine a scenario where we want to set up a timer:

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

function TimerComponent() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setTime((prevTime) => prevTime + 1);
    }, 1000);

    // Cleanup function to clear the timer
    return () => {
      clearInterval(interval);
    };
  }, []); // Empty dependency array: effect runs once, and cleanup occurs when the component unmounts

  return <div>{time} seconds have passed</div>;
}
Enter fullscreen mode Exit fullscreen mode

Here’s what’s happening:

  1. The setInterval function sets up a timer that increments time every second.
  2. The cleanup function (returned by useEffect) clears the interval when the component unmounts. This ensures that the timer doesn’t continue running after the component is removed.

Examples of useEffect Scenarios

Let’s explore some common scenarios where useEffect is particularly useful.

Fetching Data on Component Mount

Fetching data when the component mounts is one of the most common use cases for useEffect.

useEffect(() => {
  fetchData();

  async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const result = await response.json();
    setData(result);
  }
}, []); // Empty dependency array means it runs once when the component mounts
Enter fullscreen mode Exit fullscreen mode

Updating the DOM

You can use useEffect to manually manipulate the DOM after rendering, although this should be done sparingly since React manages the DOM efficiently.

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Updates the document title whenever `count` changes
Enter fullscreen mode Exit fullscreen mode

Cleanup on Component Unmount

If you have resources like subscriptions or event listeners that need to be cleaned up, you can use the return function in useEffect to handle this.

useEffect(() => {
  window.addEventListener('resize', handleResize);

  return () => {
    window.removeEventListener('resize', handleResize);
  };
}, []); // Cleanup listener when the component unmounts
Enter fullscreen mode Exit fullscreen mode

FAQs

1. What happens if I omit the dependency array in useEffect?

If you omit the dependency array, useEffect will run after every render, which can cause performance issues for expensive side effects like API calls.

2. Can I run useEffect only once?

Yes, passing an empty dependency array [] ensures that the effect runs only once after the component mounts.

3. What is the cleanup function in useEffect?

The cleanup function is a way to undo the effect when the component unmounts or before the effect runs again. It’s useful for cleaning up timers, event listeners, or subscriptions.


In conclusion, useEffect is a powerful and flexible hook that simplifies managing side effects in React. By controlling when side effects run and cleaning up when necessary, you can optimize your components and avoid unnecessary re-renders or memory leaks. Experiment with the examples above to master the art of side effect management!

Top comments (0)