What is useEffect?
useEffect
is a React hook that lets functional components perform actions after rendering. These actions can include fetching data, updating the DOM, setting up timers, or handling events. Before hooks, class components used lifecycle methods like componentDidMount
and componentWillUnmount
to do this.
With useEffect
, all these behaviors can be managed inside a functional component without needing a class.
How to use useEffect
To use useEffect
, first import it from React and then call it inside your component:
Example: Fetching Data
import { useEffect, useState } from "react";
function FetchData() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => response.json())
.then((json) => setData(json));
}, []);
return (
<div>
<h3>Post:</h3>
<p>{data ? data.title : "Loading..."}</p>
</div>
);
}
What happens here?
useEffect
runs when the component first appears on the screen.- The empty
[]
means it only runs once. - The effect fetches data and updates the state with
setData
.
When does useEffect run?
The second argument of useEffect
is called the dependency array. It tells React when to run the effect.
1. Updating Document Title
import { useEffect, useState } from "react";
function TitleUpdater() {
const [title, setTitle] = useState("React App");
useEffect(() => {
document.title = title;
}, [title]);
return (
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Update page title"
/>
);
}
- The document title updates every time
title
changes.
2. Auto-Saving Form Input
import { useEffect, useState } from "react";
function AutoSaveForm() {
const [input, setInput] = useState("");
useEffect(() => {
const timeout = setTimeout(() => {
console.log("Auto-saving input:", input);
}, 1000);
return () => clearTimeout(timeout);
}, [input]);
return (
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type something..."
/>
);
}
- Waits 1 second after typing to “auto-save” (simulated with
console.log
).
Cleaning up Effects
Some effects need cleanup, like event listeners and timers. We return a function inside useEffect
to clean up.
Example: Handling Window Resize
useEffect(() => {
const handleResize = () => {
document.body.style.backgroundColor = window.innerWidth > 800 ? "lightblue" : "lightcoral";
};
window.addEventListener("resize", handleResize);
handleResize();
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
- Dynamically changes the background color based on window width.
Example: Pausing Background Music
useEffect(() => {
const audio = new Audio("/background-music.mp3");
audio.play();
return () => {
audio.pause();
};
}, []);
- Plays background music when mounted and pauses when unmounted.
Common Mistakes with useEffect
1. Forgetting Dependencies
useEffect(() => {
fetchData();
}, []); // fetchData is missing in dependencies!
If fetchData
depends on props or state, it should be in the dependency array.
2. Causing an Infinite Loop
useEffect(() => {
setCount(count + 1); // Triggers re-render forever!
}, [count]);
This keeps updating count
, causing an endless loop.
3. Not Cleaning Up
useEffect(() => {
window.addEventListener("scroll", handleScroll);
}, []);
If not removed, this event listener stays in memory forever, leading to performance issues.
useEffect vs. useLayoutEffect
useEffect
runs after the screen updates (good for data fetching and timers).useLayoutEffect
runs before the screen updates (good for layout changes).
Example:
useLayoutEffect(() => {
document.body.style.opacity = "1";
});
Most of the time, useEffect
is the better choice.
Summary
useEffect
handles side effects in functional components.- Runs after every render, once on mount, or when dependencies change.
- Cleanup prevents memory leaks and unnecessary operations.
- Avoid missing dependencies and infinite loops.