useEffect Lifecycle and Cleanup
Functional components should remain pure—returning UI structure from input props and state. Side effects (like API requests, timer settings, and event subscriptions) must be handled inside the useEffect hook.
1. Syntax of useEffect
The useEffect hook takes a callback function containing the side effect logic, and an optional dependency array:
import { useEffect } from "react";
useEffect(() => {
// Side effect logic goes here
}, [dependency1, dependency2]);2. Dependency Array Control Modes
The dependency array controls when the side effect callback executes:
No Dependency Array
Runs the callback on the initial mount and after every single render of the component. Use this rarely, as it can cause performance bottlenecks.
useEffect(() => {
console.log("Render occurred");
});Empty Dependency Array
Runs the callback exactly once when the component first mounts (initial render). Useful for initial database loads.
useEffect(() => {
console.log("Component mounted");
}, []);With Specific Dependencies
Runs on mount, and only when one or more of the specified dependencies change.
useEffect(() => {
console.log("UserID changed, reloading details...");
}, [userId]);3. The Cleanup Function
If your side effect registers a timer, sets up a subscription, or listens to global window events, you must return a cleanup function to release resources when the component unmounts or before running the effect again.
Failing to clean up side effects causes memory leaks.
import { useState, useEffect } from "react";
function WindowResizeMonitor() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
// Add event listener on mount
window.addEventListener("resize", handleResize);
// Return cleanup function
return () => {
// Remove event listener when component unmounts
window.removeEventListener("resize", handleResize);
};
}, []); // Empty array ensures listener is set up only once
return <p>Window Width: {width}px</p>;
}4. Avoiding Infinite Loops
If you update a state variable inside useEffect and that state variable is also listed in the dependency array, you will create an infinite loop that crashes the browser:
// WARNING: Infinite Loop Example
const [count, setCount] = useState(0);
useEffect(() => {
// Triggers state change -> triggers render -> triggers effect -> loops indefinitely!
setCount(count + 1);
}, [count]);