DEV Community

Cover image for useEffect
Pritpal Singh
Pritpal Singh

Posted on

useEffect

What is useEffect?

useEffect is a React hook that allows you to perform side effects in function components. Side effects are tasks that interact with the outside world, like:

  • Fetching data from an API
  • Setting up subscriptions (e.g., WebSockets)
  • Updating the document title
  • Changing some data outside the React component (like localStorage)

In simple terms, useEffect lets your component do something after it renders or when something changes.

Basic Syntax

useEffect(() => {
  // Code to run after the component renders or updates
});
Enter fullscreen mode Exit fullscreen mode

This useEffect runs after every render by default.

Common Use Cases for useEffect:

  1. Fetching data from an API
  2. Subscribing to an event or updating an external system
  3. Cleaning up resources (like clearing timers)

Example 1: Basic Usage

Let’s start with an example where we update the page title with the current counter value.

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

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

  // Using useEffect to update the document title
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

How does this work?

  • useEffect runs after every render: Every time you click the button and the count changes, useEffect runs and updates the document title.

Example 2: Controlling When useEffect Runs (Dependency Array)

Sometimes you don’t want the useEffect to run on every render, only when certain values change. You can control this using the dependency array.

useEffect(() => {
  // Code to run after component renders or when dependencies change
}, [dependency1, dependency2]);
Enter fullscreen mode Exit fullscreen mode
  • If one of the dependencies in the array changes, useEffect will run again.
  • If the array is empty [], useEffect will only run once when the component first renders.

Example: Only Run useEffect Once

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

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

  // This useEffect will only run once when the component first renders
  useEffect(() => {
    console.log('Component mounted!');
  }, []);  // Empty dependency array

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example:

  • The useEffect runs only once when the component mounts (i.e., when it is first added to the page).
  • It won’t run again, even if you click the button and update the count value.

Example 3: Using useEffect with Cleanup

Sometimes, components set up subscriptions or timers that need to be cleaned up when the component is removed (or unmounted). You can do this with useEffect by returning a cleanup function.

Example: Cleaning up a Timer

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

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // Cleanup function to clear the interval when the component unmounts
    return () => clearInterval(interval);
  }, []);  // Empty dependency array to run only on mount/unmount

  return (
    <div>
      <p>Timer: {seconds} seconds</p>
    </div>
  );
}

export default Timer;
Enter fullscreen mode Exit fullscreen mode

How does this work?

  • Set up a timer: The setInterval function runs every second and updates the seconds state.
  • Cleanup: When the component is removed, clearInterval is called to stop the timer. The cleanup function is returned inside useEffect.

Summary of useEffect Behavior:

  • Run on every render: If you don't include a dependency array.
   useEffect(() => {
     // Runs after every render
   });
Enter fullscreen mode Exit fullscreen mode
  • Run only once (when the component mounts):
   useEffect(() => {
     // Runs only once, after the initial render
   }, []);
Enter fullscreen mode Exit fullscreen mode
  • Run when certain values change:
   useEffect(() => {
     // Runs when `count` changes
   }, [count]);
Enter fullscreen mode Exit fullscreen mode
  • Cleanup: Return a cleanup function to stop or remove things (like timers or subscriptions) when the component unmounts.
   useEffect(() => {
     // Code to set up something

     return () => {
       // Code to clean up (runs when component unmounts)
     };
   }, []);
Enter fullscreen mode Exit fullscreen mode

Key Points:

  • useEffect lets you perform side effects in your components.
  • By default, it runs after every render, but you can control when it runs using a dependency array.
  • Always remember to clean up resources like timers or subscriptions in the return function of useEffect to avoid memory leaks.

The lesson I provided covers the core concepts of useEffect, but there's more to learn about it, especially in advanced cases. Here's a list of additional details that can be helpful to understand useEffect thoroughly:

Additional Key Concepts for useEffect

  1. Multiple useEffect Hooks:
    • You can have more than one useEffect in a single component to handle different side effects independently.
   useEffect(() => {
     console.log("Effect 1");
   }, []);

   useEffect(() => {
     console.log("Effect 2");
   }, []);
Enter fullscreen mode Exit fullscreen mode
  • These will run separately, allowing you to organize your side effects better.
  1. Conditional Logic Inside useEffect:
    • You can put conditional logic inside useEffect to only perform side effects based on certain conditions.
   useEffect(() => {
     if (someCondition) {
       // Execute side effect only if condition is met
     }
   }, [someCondition]);
Enter fullscreen mode Exit fullscreen mode
  1. Handling Async Code:
    • useEffect doesn't directly support async functions, but you can call async functions inside it. This is common for data fetching.
   useEffect(() => {
     const fetchData = async () => {
       const result = await fetch('https://api.example.com/data');
       console.log(result);
     };
     fetchData();
   }, []);
Enter fullscreen mode Exit fullscreen mode
  1. Use with Dependencies: Pitfalls and Solutions:
    • Always ensure that all dependencies (variables or state) used inside the useEffect are listed in the dependency array. This prevents stale closures, where the effect uses old values instead of updated ones.
   useEffect(() => {
     console.log(someVar); // If `someVar` changes, the effect should re-run
   }, [someVar]);
Enter fullscreen mode Exit fullscreen mode
  • Pitfall: Missing a dependency can lead to bugs, like the effect not re-running when it should.
  • Solution: Use ESLint's React rules, which will warn you about missing dependencies.
  1. Re-running the Effect:

    • useEffect re-runs if any dependency changes. If you need a side effect that runs only once on mount or only when certain values change, manage it with the dependency array.
  2. Common Use Cases for Cleanup:

    • Event listeners: Adding and removing event listeners (e.g., window.addEventListener).
    • WebSocket connections: Starting a WebSocket connection and closing it.
    • Timers: Clearing timeouts/intervals when the component unmounts.
  3. Performance Considerations:

    • useEffect can potentially cause performance issues if not used properly. For example, running an expensive operation or a data-fetching task on every render may slow down the app.
    • Optimize by using dependency arrays and breaking complex logic into multiple effects.
  4. Execution Order:

    • Effects run after the render but before the DOM is painted.
    • Cleanup functions (if any) run before the component is removed from the UI or before the next effect runs.
  5. React Strict Mode and Double Execution:

    • In React's strict mode (used in development), useEffect runs twice (mount/unmount and then mount again) to help identify potential issues. This doesn’t happen in production.
  6. Handling Race Conditions in Async Effects:

    • If you trigger an API call within useEffect, but the component unmounts before the request completes, you could end up updating the state of an unmounted component (causing a memory leak).
    • Solution: Abort the API request or use a cancelled flag.
    useEffect(() => {
      let isCancelled = false;
      const fetchData = async () => {
        const response = await fetch('https://api.example.com');
        if (!isCancelled) {
          // Update state here only if the component is still mounted
        }
      };
      fetchData();
      return () => {
        isCancelled = true; // Cleanup to prevent updating unmounted component
      };
    }, []);
    

However, for complete mastery of useEffect, you should also understand:

  • Handling async operations and race conditions
  • Performance considerations in larger apps
  • Advanced use cases with multiple useEffect hooks and complex state
  • React's behavior in strict mode (e.g., double execution in dev mode)

Top comments (0)