Mastering React useEffect: The Complete Guide to Avoid Common Pitfalls
This guide explains what useEffect does, its three core execution rules, how dependency arrays change its behavior, eight practical scenarios, six typical pitfalls with concrete code examples, and best‑practice recommendations for writing clean, leak‑free React functional components.
1. What is useEffect?
useEffect is the Hook used in React function components to handle side effects such as data fetching, timers, subscriptions, DOM manipulation, and local storage.
API requests, data fetching
Timers, delays
Event subscriptions, DOM listeners
Manual DOM operations
Local storage read/write
The core execution rules are:
Runs after the component mounts for the first time.
Runs when any value in the dependency array changes.
Runs the cleanup function before the component unmounts or before re‑execution.
useEffect(() => {
// side‑effect logic
return () => {
// cleanup logic (runs on unmount / before re‑run)
};
}, [dependencyArray]);2. Dependency array determines behavior
1. No dependency array – runs on every render
useEffect(() => {
console.log("Component renders each time");
});Rarely needed; can cause performance waste.
2. Empty array – runs once (componentDidMount)
useEffect(() => {
console.log("Runs only on first render");
}, []);Typical for initial data requests or one‑time configuration.
3. With dependencies – runs when they change
useEffect(() => {
console.log("Runs when userId changes");
}, [userId]);Common for refetching on parameter changes, linking logic, or watching state.
3. Common useEffect scenarios
Scenario 1: Initial data fetch
useEffect(() => {
// async request
const fetchData = async () => {
const res = await fetch("/api/list");
const data = await res.json();
setList(data);
};
fetchData();
}, []);Scenario 2: Refetch when a parameter changes
useEffect(() => {
fetchListByKeyword(keyword);
}, [keyword]);Scenario 3: Timer / interval with cleanup
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(timer);
}, []);Scenario 4: DOM manipulation / third‑party library init
useEffect(() => {
const dom = document.getElementById("chart");
const myChart = echarts.init(dom);
myChart.setOption(option);
return () => myChart.dispose();
}, [option]);Scenario 5: Global event listeners
useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);Scenario 6: Sync props to local state
useEffect(() => {
setLocalData(props.data);
}, [props.data]);Scenario 7: Cleanup to prevent memory leaks
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const res = await fetch("/api/data", { signal: controller.signal });
const data = await res.json();
setData(data);
} catch (err) {
console.log("Request cancelled");
}
};
fetchData();
return () => controller.abort();
}, []);Scenario 8: Full lifecycle mapping
Mount: useEffect(() => {}, []) Cleanup: return a cleanup function
Update:
useEffect(() => {}, [deps])4. Six frequent pitfalls
Pitfall 1: Missing dependencies
// Wrong – keyword omitted from deps
const [keyword, setKeyword] = useState("");
useEffect(() => {
search(keyword);
}, []); // never updates searchSolution: include every variable used inside the effect in the dependency array.
Pitfall 2: Infinite loop
Reference types in deps cause new identity each render.
Updating state inside the effect triggers a re‑run.
// Infinite loop example
const [list, setList] = useState([]);
useEffect(() => {
setList([1, 2, 3]); // state update → re‑render → effect runs again
}, [list]);Solution: use primitive values, memoize objects/arrays with useMemo or useCallback, and avoid putting the state being set into the deps.
Pitfall 3: Not cleaning up – memory leak
Console warning:
Can't perform a React state update on an unmounted component.Cause: timers, requests, or listeners continue after the component is unmounted and try to update state.
Solution: always provide a cleanup function for every side effect.
Pitfall 4: Stale closure from function defined outside
// Wrong – function defined outside captures old closure
const fetchData = () => {
console.log(userId);
};
useEffect(() => {
fetchData();
}, []);Solution: define the function inside the effect or memoize it with useCallback and add it to the deps.
Pitfall 5: Async function directly as effect callback
// Wrong – async effect returns a Promise
useEffect(async () => {
const data = await fetchData();
}, []);Solution: declare an async function inside the effect and invoke it.
Pitfall 6: Hard‑coded dependency arrays
Using [{}] or [[]] creates a new object/array on every render, so the effect never sees a matching dependency and never updates.
5. Best practices
Let each useEffect handle a single concern; avoid mixing unrelated logic.
Always write a cleanup function for timers, requests, listeners, and third‑party instances.
Strictly list all dependencies; do not silence ESLint warnings.
Prefer state‑derived calculations over effects when possible.
For async requests, include loading state, error handling, and an abort controller.
Memoize component functions with useCallback and objects/arrays with useMemo to prevent unnecessary re‑executions.
6. Conclusion
useEffectessentially watches dependency changes, runs side effects, and cleans up at the appropriate time. The challenge lies in following its rules, understanding execution timing, and habitually cleaning up. Mastering the scenarios and pitfalls described here enables you to write stable, maintainable, and pitfall‑free React code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
