React useEffect Hook

React useEffect Hook

The useEffect Hook allows you to perform side effects in your functional components.

In React, a "side effect" is any operation that affects something outside the scope of the function being executed. Examples of side effects include:


Importing useEffect

Just like useState, useEffect is a built-in Hook that you import from React:

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

Basic Syntax

The useEffect hook takes two arguments:

  1. A callback function containing the side-effect logic.
  2. An optional dependency array that determines when the effect should run.
useEffect(() => {
  // Side effect logic goes here
}, [dependencies]);

Controlling When useEffect Runs

The dependency array is incredibly powerful. Depending on how you configure it, you can control exactly when your effect fires.

1. No Dependency Array

If you do not provide a dependency array, the effect will run after every single render of the component. This can easily lead to infinite loops if you update state inside the effect!

useEffect(() => {
  console.log("I run after every single render!");
});

2. An Empty Dependency Array []

If you provide an empty array, the effect will run only once, immediately after the component mounts for the first time. This is the equivalent of componentDidMount in older Class components, making it perfect for initial data fetching.

useEffect(() => {
  console.log("I only run once when the component mounts!");
  // Perfect place to fetch initial data from an API
}, []);

3. Array with Specific Dependencies [prop, state]

If you add variables to the array, the effect will only run when the component mounts AND whenever any of those specific dependencies change between renders.

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

useEffect(() => { // This runs on mount, and whenever 'count' changes. document.title = You clicked ${count} times; }, [count]); // 'count' is the dependency


Effect Cleanup Functions

Some effects create resources that need to be cleaned up before the component unmounts (leaves the screen) or before the effect runs again. Examples include clearing a timer or removing an event listener to prevent memory leaks.

To clean up an effect, your callback function should return a cleanup function.

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

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

useEffect(() => { // Set up the interval const intervalId = setInterval(() => { setSeconds(prev => prev + 1); }, 1000);

<span style="color: #6A9955;">// Return the cleanup function</span>
<span style="color: #569CD6;">return</span> () <span style="color: #569CD6;">=&gt;</span> {
  <span style="color: #FF69B4;">clearInterval</span>(<span style="color: #008c8f;">intervalId</span>); <span style="color: #6A9955;">// Cleanup the interval on unmount</span>
  <span style="color: #FF69B4;">console</span>.<span style="color: #FF69B4;">log</span>(<span style="color: #CE9178;">"Timer cleanup!"</span>);
};

}, []); // Empty array means this runs once on mount

return <h1>Seconds elapsed: {seconds}</h1>; }

When Does the Cleanup Function Run?

  1. Right before the component is completely unmounted from the DOM.
  2. Right before the effect re-runs (if it has dependencies that changed). This ensures the old effect is cleaned up before a new one starts.

Common Pitfall: The Dependency Array is strict. If your effect uses props or state variables, you must include them in the dependency array. If you omit them, your effect might use stale (outdated) values.


Exercise

?

If you want a useEffect block to fetch API data exactly once when the component first loads, how should you structure the dependency array?