Do you find understanding the useEffect hook in React hard? Honestly, I did too.
When I first started learning React, useEffect looked simple at first, but things like dependencies and cleanup made it confusing pretty quickly.
So let's break it down.
What is useEffect?
useEffect is a built-in React Hook that lets functional components perform side effects.
A side effect is basically something your component does outside of simply rendering the UI.
For example:
- Fetching data from an API
- Starting a timer
- Adding an event listener
- Updating the page title
- Connecting to an external service
Basic Format
useEffect(() => {
// Effect code
return () => {
// Cleanup code
};
}, []);
There are three main parts.
1. The Effect
This is the function containing the code you want to run.
useEffect(() => {
console.log("Effect ran!");
}, []);
By default, useEffect runs after React renders the component.
2. The Dependency Array
The second part is the dependency array:
useEffect(() => {
console.log(`Count is ${count}`);
}, [count]);
It tells React when the effect needs to run again.
Empty array
useEffect(() => {
console.log("Effect ran");
}, []);
The effect doesn't re-run because of changing dependencies.
Note: In development, React Strict Mode can run an effect's setup and cleanup more than once, so don't panic if you see your console log twice.
With a dependency
useEffect(() => {
console.log(`Count is ${count}`);
}, [count]);
The effect runs again when count changes.
No dependency array
useEffect(() => {
console.log("Effect ran");
});
The effect runs after every render.
A quick way to remember:
[] → no dependency-based re-runs
[count] → runs when count changes
(no array) → runs after every render
3. The Cleanup Function
The cleanup function is used to stop or remove something that your effect started.
For example, if you start a timer:
useEffect(() => {
const timer = setInterval(() => {
console.log("Running...");
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
When the component disappears, the cleanup function stops the timer.
Cleanup is commonly used for:
- Timers
- Event listeners
- Subscriptions
- Connections
Think of it like this:
Effect = Set something up
Cleanup = Clean it up
A Simple Example
Here's a basic counter:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count is ${count}`);
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Whenever count changes, React renders again, notices that the dependency changed, and runs the effect.
Pretty simple, right?
But this is where people often run into problems. Because there are some common mistakes you should know about.
Common Mistakes
Mistake 1: Forgetting Cleanup
Imagine you create a timer but never stop it:
useEffect(() => {
const timer = setInterval(() => {
console.log("Hello");
}, 1000);
}, []);
A better version is:
useEffect(() => {
const timer = setInterval(() => {
console.log("Hello");
}, 1000);
return () => clearInterval(timer);
}, []);
Whenever your effect creates something that needs to be stopped or removed, remember to clean it up.
Mistake 2: Unstable Dependencies
Objects, arrays, and functions are compared by reference, not their contents.
For example:
const user = {
id: 193,
name: "Rubi"
};
useEffect(() => {
fetchUser(user);
}, [user]);
If user is recreated on every render, React sees a new object reference and may run the effect again.
If you only need the ID, depend on the primitive value instead:
useEffect(() => {
fetchUser(user.id);
}, [user.id]);
Now the effect only needs to re-run when user.id changes.
Mistake 3: Using useEffect for Everything
Sometimes you don't need useEffect at all.
For example, don't use an effect just to respond to a button click:
const [submitted, setSubmitted] = useState(false);
useEffect(() => {
if (submitted) {
postForm(formData);
}
}, [submitted]);
<button onClick={() => setSubmitted(true)}>
Submit
</button>
You can simply do:
<button onClick={() => postForm(formData)}>
Submit
</button>
If something happens directly because of a user action, an event handler is usually the better choice.
Similarly, if you need to measure or adjust the DOM before the browser paints, useLayoutEffect may be more appropriate.
Wrapping Up
useEffect is actually pretty simple once you understand its three main parts:
Effect → the code you want to run.
Dependencies → when the effect should run again.
Cleanup → what needs to be stopped or removed.
And before writing an effect, ask yourself:
“Do I actually need
useEffecthere?”
Once you understand when to use it, what it depends on, and how to clean it up, useEffect becomes a lot less scary.
Top comments (2)
Interesting!!
Beautiful project