Understanding useEffect Execution Mechanism and Cleanup in React
This article explains the purpose of React's useEffect hook, details how dependency arrays control its execution timing, demonstrates cleanup functions, and provides practical code examples for handling side effects such as data fetching, timers, and event listeners.
In React, side effects are operations that interact with the environment outside the component's pure rendering process, such as network requests, subscriptions, or DOM manipulation. The useEffect hook is provided to manage these side effects and optionally clean them up.
What is useEffect? It is a React Hook that runs after the component has rendered. It receives a callback function where side‑effect logic is placed, and an optional dependency array that determines when the effect should re‑run.
Execution mechanism depends on the dependency array:
No dependency array : the effect runs after every render.
Empty dependency array : the effect runs only once after the initial mount, useful for initialization tasks.
Specific dependencies : the effect re‑runs only when one of the listed values changes.
Example without dependencies:
useEffect(() => {
console.log('Runs on every render');
});Example with an empty array:
useEffect(() => {
console.log('Runs only on mount');
}, []);Example with a specific dependency:
useEffect(() => {
console.log('Runs when dep changes');
}, [dep]);Cleaning up side effects is done by returning a function from the effect callback. This cleanup runs before the component unmounts or before the effect re‑executes due to changed dependencies.
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
return () => {
clearInterval(timer);
console.log('Timer cleared');
};
}, []);Typical use‑case: fetching data on mount and storing it in state:
import React, { useState, useEffect } from 'react';
const URL = 'http://example.cn';
function App() {
const [list, setList] = useState([]);
useEffect(() => {
async function getData() {
const res = await fetch(URL);
const json = await res.json();
setList(json.data.channels);
}
getData();
}, []);
return (
App Component
{list.map(item =>
{item.name}
)}
);
}When a dependency is dynamic, include it in the array so the effect updates accordingly:
const [stateValue, setStateValue] = useState(0);
useEffect(() => {
getData(stateValue);
}, [stateValue]);Important notes : All values used inside the effect that come from component state or props must be listed in the dependency array to avoid stale closures. Omitting them can cause the effect to capture outdated values.
Example of a stale‑closure pitfall with a resize listener that logs a count value demonstrates why the count should be added to the dependency array.
useEffect(() => {
const handle = () => console.log(`Current count: ${count}`);
window.addEventListener('resize', handle);
return () => window.removeEventListener('resize', handle);
}, [count]);In summary, using useEffect correctly—choosing the appropriate dependency array and providing cleanup functions—helps manage side effects efficiently, keeps code maintainable, and prevents memory leaks or unexpected behavior in React applications.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
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.