The useEffect
hook is one of the most powerful and widely used hooks in React. It allows you to handle side effects in your functional components, such as data fetching, subscriptions, manual DOM manipulation, and more. Before hooks were introduced, handling side effects was only possible in class components using lifecycle methods like componentDidMount
, componentDidUpdate
, and componentWillUnmount
. With the useEffect
hook, you can handle all of these behaviors in functional components.
1. What is the useEffect
Hook?
The useEffect
hook is used to perform side effects in functional components. Side effects are operations that can affect other components or interact with the outside world, such as:
- Fetching data from an API
- Subscribing to a service
- Updating the DOM directly
- Setting up event listeners
- Cleaning up resources when the component unmounts
The useEffect
hook runs after the component renders, and it runs by default after every render.
2. Syntax of useEffect
Hook
useEffect(() => {
// Your side effect logic here
}, [dependencies]);
- The first argument is a callback function that contains the side effect logic.
- The second argument is an optional dependency array that tells React when to re-run the effect (i.e., if the dependencies change). If no dependencies are passed, the effect runs after every render. If the array is empty, the effect runs only once when the component mounts.
3. Basic Example of useEffect
Hook
Let’s look at a basic example where we fetch data from an API using useEffect
and display it in a component:
import React, { useState, useEffect } from 'react';
function FetchData() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []); // Empty dependency array, so this runs only once when the component mounts
return (
<div>
{loading ? <p>Loading...</p> : <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
export default FetchData;
In this example:
- The
useEffect
hook fetches data from an API when the component mounts. - The empty dependency array (
[]
) ensures that the effect runs only once when the component is mounted (similar tocomponentDidMount
in class components).
4. Dependencies Array in useEffect
The second argument of useEffect
is an optional array of dependencies that allows you to control when the effect should re-run. The effect will only re-run if one of the dependencies has changed since the last render.
Example with Dependencies:
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
// Cleanup the timer when the component unmounts
return () => clearInterval(timer);
}, []); // Empty array ensures this runs once when the component mounts
return <p>Timer: {seconds} seconds</p>;
}
export default Timer;
In this example:
- The effect sets up a timer that updates the
seconds
state every second. - The cleanup function
clearInterval
is returned to stop the timer when the component unmounts, preventing memory leaks.
Example with Dependencies:
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('');
useEffect(() => {
if (count > 5) {
setMessage('Count is greater than 5');
} else {
setMessage('');
}
}, [count]); // Effect runs every time `count` changes
return (
<div>
<p>Count: {count}</p>
<p>{message}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
In this example:
- The effect is dependent on
count
, so it will only run whencount
changes.
5. Cleaning Up Effects
If your side effect involves something that needs to be cleaned up (like subscriptions, timers, or manual DOM manipulation), you can return a cleanup function inside your useEffect
callback. This cleanup function is called when the component is unmounted or before the effect runs again (if the dependencies have changed).
Example with Cleanup:
import React, { useState, useEffect } from 'react';
function Component() {
const [status, setStatus] = useState('offline');
useEffect(() => {
// Simulate a subscription or event listener
const interval = setInterval(() => {
console.log('Checking online status...');
}, 1000);
// Cleanup function to clear the interval
return () => {
clearInterval(interval);
};
}, []); // Empty array ensures this effect runs only once when the component mounts
return <p>Status: {status}</p>;
}
export default Component;
In this example:
- The effect sets up an interval, but when the component unmounts, the cleanup function clears the interval, preventing a potential memory leak.
6. Running Effects on Mount and Unmount
You can simulate behaviors similar to componentDidMount
and componentWillUnmount
using useEffect
.
- To run an effect only once when the component mounts, pass an empty dependency array
[]
:useEffect(() => { console.log('Component mounted'); }, []);
- To simulate componentWillUnmount, return a cleanup function that will be called when the component unmounts:
useEffect(() => { return () => { console.log('Component unmounted'); }; }, []);