In React, the primary job of a component is to render UI based on state and props. Any operation that falls outside of this primary rendering process is considered a Side Effect.
The useEffect Hook is the tool used in functional components to manage these side effects, ensuring they are executed at the correct time in the component’s lifecycle (e.g., after the component mounts, when it updates, or when it unmounts).
1. Common Side Effects
Side effects typically involve synchronizing your component with an external system. Common use cases include:
- Data Fetching: Retrieving data from an external API endpoint.
- DOM Manipulation: Manually changing the document title (
document.title). - Subscriptions: Setting up event listeners or timers.
- Logging: Sending information to external analytics services.
2. The useEffect Syntax and Execution Times
The useEffect Hook accepts two arguments:
- A function (the effect): This is the code that performs the side effect.
- A dependency array (optional): This controls when the effect is re-run.
Syntax:
useEffect(() => {
// Code to run (the side effect)
return () => {
// Cleanup function (runs when the component unmounts or before the next effect runs)
};
}, [dependency1, dependency2]);
3. Controlling Effect Execution via the Dependency Array
The behavior of useEffect is determined entirely by the second argument—the dependency array ([]):
| Dependency Array Status | When the Effect Runs | Use Case |
| No Array (omitted) | Runs after every single render (mount and every update). | Avoid this in most scenarios, as it can easily lead to infinite loops. |
Empty Array ([]) | Runs only once, after the initial mount of the component. | Data fetching, setting up global event listeners, or initializing third-party libraries. |
With Values ([state, props]) | Runs after the initial mount AND whenever any of the values listed in the array change. | Fetching data when an ID changes, or recalculating data when user input changes. |
4. Execution Example: Data Fetching and Cleanup
Let’s simulate fetching user data once, only when the component first mounts.
In your my-first-app/src directory, open App.js and replace its content with the following code:
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState("Loading...");
// This useEffect will run ONLY ONCE after the component mounts, due to the empty array [].
useEffect(() => {
// 1. Side Effect: Data Fetching Simulation
console.log("Effect: Component mounted. Fetching data...");
// Simulate an asynchronous API call
const timer = setTimeout(() => {
setData("User Data: ID 404, Status Active");
console.log("Effect: Data successfully updated.");
}, 2000);
// 2. Cleanup Function (Optional but important for certain effects)
return () => {
// Runs when the component is unmounted (e.g., navigating away).
// Clears the timer to prevent memory leaks if the component unmounts before the timer fires.
clearTimeout(timer);
console.log("Cleanup: Timer cleared.");
};
}, []); // Empty dependency array means run once on mount
return (
<div className="EffectDemo">
<h1>`useEffect` Demo</h1>
<p>{data}</p>
</div>
);
}
export default DataFetcher;
- Local Execution: Run
npm startin your terminal. - Observation:
- The message “Loading…” appears immediately.
- After 2 seconds, the state is updated to the fetched data.
- The
useEffectblock runs only once, demonstrating how to control its execution timing.
5. useEffect with Dependencies
To run a side effect whenever a state or prop changes, you list that variable in the dependency array.
Example: Fetching a new user every time the userId state changes:
useEffect(() => {
console.log(`Fetching data for new ID: ${userId}`);
// Data fetching logic based on userId...
}, [userId]); // Effect runs when userId changes
