Introduction to useEffect
The useEffect
hook is a fundamental part of building React applications with functional components. It allows you to perform side effects in your components. Side effects are operations that interact with the outside world, such as data fetching, subscriptions, manually changing the DOM, or setting up event listeners.
Before hooks, you'd typically handle these in lifecycle methods like componentDidMount
, componentDidUpdate
, and componentWillUnmount
in class components. useEffect
consolidates this logic into a single, more intuitive API.
Basic Syntax
The useEffect
hook accepts two arguments:
- A function that contains the side-effect logic.
- An optional dependency array.
import React, { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// This function runs after every render of the component
// (unless dependencies are specified).
console.log('Component rendered or updated!');
}); // No dependency array means it runs after every render
return <div>Hello, useEffect!</div>;
}
Understanding the Dependency Array
The dependency array is crucial for controlling when your effect runs. It tells React to re-run the effect only when the values in this array change.
1. No Dependency Array (Runs on Every Render)
If you omit the second argument, the effect will run after every render of the component. This is rarely what you want, as it can lead to performance issues and infinite loops.
useEffect(() => {
console.log('This runs after EVERY render!');
});
2. Empty Dependency Array (Runs Once on Mount)
Passing an empty array []
as the second argument tells React to run the effect only once after the initial render (component mount) and never again on re-renders. This is similar to componentDidMount
.
useEffect(() => {
console.log('This runs only once on component mount!');
// Ideal for initial data fetching, setting up subscriptions
}, []); // Empty array
3. With Dependencies (Runs on Mount and When Dependencies Change)
If you include variables in the dependency array, the effect will re-run whenever any of those variables change between renders. This is the most common use case.
useEffect(() => {
console.log(`The count is now: ${count}`);
}, [count]); // Effect re-runs when 'count' changes
The Cleanup Function
Sometimes, your side effect needs a "cleanup" phase. For example, if you set up a subscription or an event listener, you need to unsubscribe or remove the listener when the component unmounts or before the effect re-runs. You can do this by returning a function from your useEffect
callback.
useEffect(() => {
console.log('Effect is running...');
// This function is the cleanup function
return () => {
console.log('Cleanup function is running!');
// Perform cleanup here (e.g., unsubscribe, clear timers)
};
}, []); // Runs once on mount, cleanup runs on unmount
The cleanup function runs:
- Before the effect re-runs (if dependencies change).
- When the component unmounts.
Real-World Examples
Example 1: Fetching Data on Component Mount
A classic use case is fetching data when the component first renders.
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUser = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // Re-fetch when userId changes
if (loading) return <p style="margin-bottom: 15px; line-height: 1.7;">Loading user data...</p>;
if (error) return <p style="color: red; margin-bottom: 15px; line-height: 1.7;">Error: {error}</p>;
if (!user) return <p style="margin-bottom: 15px; line-height: 1.7;">No user found.</p>;
return (
<div>
<h3 style="color: #2980b9; font-size: 1.5em; margin-top: 0; border-bottom: 1px solid #2980b9; padding-bottom: 5px; margin-bottom: 15px;">User Profile</h3>
<p style="margin-bottom: 15px; line-height: 1.7;"><strong style="color: #e67e22;">Name:</strong> {user.name}</p>
<p style="margin-bottom: 15px; line-height: 1.7;"><strong style="color: #e67e22;">Email:</strong> {user.email}</p>
<p style="margin-bottom: 15px; line-height: 1.7;"><strong style="color: #e67e22;">Phone:</strong> {user.phone}</p>
</div>
);
}
Example 2: Updating Document Title
You can use useEffect
to interact with the browser's DOM, like changing the document title based on component state.
import React, { useState, useEffect } from 'react';
function TitleUpdater() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
console.log('Document title updated!');
}, [count]); // Re-run when count changes
return (
<div>
<p style="margin-bottom: 15px; line-height: 1.7;">You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Example 3: Setting Up and Cleaning Up Event Listeners
This demonstrates using the cleanup function to prevent memory leaks by removing event listeners when the component unmounts.
import React, { useState, useEffect } from 'react';
function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
console.log('Event listener added!');
// Cleanup function
return () => {
window.removeEventListener('mousemove', handleMouseMove);
console.log('Event listener removed!');
};
}, []); // Empty dependency array: runs once on mount, cleans up on unmount
return (
<div>
<p style="margin-bottom: 15px; line-height: 1.7;">Mouse X: {position.x}, Mouse Y: {position.y}</p>
</div>
);
}
useEffect
is a powerful hook that allows you to manage side effects in your React functional components effectively. By understanding its two arguments—the effect function and the dependency array—along with the optional cleanup function, you can write cleaner, more predictable, and more performant React code. Always remember to consider your dependencies carefully to avoid unnecessary re-runs or stale closures.