React Component Lifecycle
Understand the modern React lifecycle across render, commit, passive effects, and unmount phases.
TL;DR
- Divide component execution into
renderand commit phases. - Run side effects and subscriptions after paint with
useEffect. - Tear down active timers and event listeners upon
unmount.
Modern Lifecycle Phases
Mounting Phase EffectExecute setup once after component inserts into DOM.
useEffect(() => {
fetchInitialData();
}, []);Updating Phase DependenciesRe-run effect only when specified values change.
useEffect(() => {
syncUserWithDisk(userId);
}, [userId]);Unmounting Cleanup TeardownReturn function to execute upon component removal.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);Effect Timing and Layout Effects
Passive useEffect TimingRuns asynchronously after browser layout and paint.
// Non-blocking: fires after paint
useEffect(() => {
logAnalyticsEvent();
});Synchronous useLayoutEffectFires synchronously before browser paints to prevent flicker.
useLayoutEffect(() => {
const rect = ref.current?.getBoundingClientRect();
setHeight(rect?.height ?? 0);
}, []);Insertion Effect for CSS-in-JSInject styles before any DOM mutations occur.
useInsertionEffect(() => {
injectGlobalCSSRules();
}, []);Strict Mode and Cleanup Symmetry
Symmetric Event ListenerAdd and remove browser listeners identically.
useEffect(() => {
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, []);AbortController Network TeardownCancel in-flight fetch requests upon unmount.
useEffect(() => {
const c = new AbortController();
fetch(url, { signal: c.signal });
return () => c.abort();
}, [url]);State Reset via Key ChangeRemount entire component fresh by changing key.
// Reset component state completely
<UserProfile key={selectedUserId} />Lifecycle Antipatterns
Render Body Side EffectsMutating external state during render breaks React.
// BAD: Side effect in render
localStorage.setItem('visited', 'true');Redundant Mount SettersDerive values directly in render instead of mount effects.
// BAD: Mount effect setter
useEffect(() => { setFullName(first + last); }, []);Uncleaned Background IntervalsFailing to return cleanup creates memory leaks.
// BAD: Interval continues after unmount
useEffect(() => { setInterval(poll, 1000); }, []);Tips
- Always write symmetric cleanup functions in
useEffectso components withstand React 18 Strict Mode double-mounting gracefully. - Reserve
useLayoutEffectstrictly for synchronous DOM measurements and critical layout calculations before the browser paints to eliminate visual flickering.
Warnings
- Never perform side effects or mutate variables during the pure
rendercalculation phase of a component. - Avoid calling state setters synchronously inside mount effects because it forces immediate second
re-renderpasses.
In Practice
Implement a responsive window size listener adhering strictly to mount and unmount symmetry.
- Initialize width and height state with current dimensions.
- Attach resize event listener inside passive useEffect.
- Return cleanup callback removing listener symmetrically.
- Verify that component teardown leaves no lingering listeners.
export function WindowTracker() {
const [width, setWidth] = useState(0);
useEffect(() => {
const onResize = () => {
setWidth(window.innerWidth);
};
onResize();
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
};
}, []);
return <CardTitle>Width: {width}px</CardTitle>;
}FAQ
The render phase computes JSX output and compares virtual DOM trees purely. The commit phase applies changes to actual browser DOM and fires effects.
React intentionally mounts, unmounts, and remounts components in development to verify that cleanup functions restore system state symmetrically.
Use useLayoutEffect only when you need to read DOM measurements (like element height) and update state before the browser paints to prevent visual flickering.