useEffect is a React Hook used for side effects — things like API calls, timers, event listeners, and updating the document title. Its second argument, the dependency array, controls when the effect runs.
1. Run Only Once
useEffect(() => {
console.log("Component Mounted");
}, []);
Why? An empty array tells React that the effect does not depend on any state or prop, so it runs only after the component first appears on screen.
2. Run on Every Render
useEffect(() => {
console.log("Rendered");
});
No dependency array is provided.
React runs this effect after every render — on first mount and after every state or prop change.
3. Run When State Changes
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Count changed");
}, [count]);
- Dependency array: [count].
- The effect runs on mount and again whenever count changes.
- If you use [] instead, "Count changed" prints only once on mount — not when count updates
4. Two States, One Effect
const [count, setCount] = useState(0);
const [name, setName] = useState("");
useEffect(() => {
console.log("count or name changed");
}, [count, name]);
- Dependency array: [count, name].
- The effect runs when either count or name changes.
- If only [count] is added, changing name will not run this effect. Also, never omit a value used inside the effect — it can cause stale-value bugs.
- Derived Logic
<p>{count % 2 === 0 ? "Even Number" : "Odd Number"}</p>
- Put this logic inside render, not inside useEffect.
- Why? It is directly calculated from existing state. React can re-render and show the correct value automatically. useEffect is for side effects outside React’s normal rendering flow, such as API calls or DOM listeners.
6. API Call on Mount
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => setUsers(data));
}, []);
- We use [] because users should be fetched only once when the component loads.
- If you remove [], the API call runs after every render, causing unnecessary network requests
- Infinite Loop Trap
useEffect(() => {
setCount(count + 1);
}, [count]);
This creates an infinite loop.
Why? The effect runs → count changes → React re-renders → dependency count changed → effect runs again → count changes again. This repeats forever
- Fix the Infinite Loop
If you want to increment only once:
useEffect(() => {
setCount((prev) => prev + 1);
}, []);
- Use an empty dependency array [].
- Use the functional update setCount((prev) => prev + 1) so you always update from the latest state value.
- Do not put count in the dependency array if the effect itself changes count.
9. Document Title Sync
useEffect(() => {
document.title = count;
}, [count]);
- Put count inside the dependency array.
- Why? The effect uses count, and the title should update every time count changes.
10. Cleanup Basic
useEffect(() => {
window.addEventListener("click", handleClick);
});
Problem
Every render adds another click listener. Old listeners are never removed, which can cause memory leaks and repeated function calls.
Correct Way
useEffect(() => {
const handleClick = () => {
console.log("Window clicked");
};
window.addEventListener("click", handleClick);
return () => {
window.removeEventListener("click", handleClick);
};
}, []);
- The returned function is the cleanup function.
- Cleanup runs before the next effect execution and when the component unmounts.
Quick Dependency Table
| Dependency array | When effect runs |
|---|---|
| No array | After every render react |
| [] | Once after first render / mount react+1 |
| [count] | On mount and when count changes react |
| [count, name] | On mount and when either changes react |
Simple Rule to Remember
- Need something once when page loads? Use [].
- Need something whenever a value changes? Add that value: [value].
- Need something after every render? Omit the array.
- Added a listener, timer, or subscription? Always return a cleanup function.
Top comments (0)