SyntaxStudy
Sign Up
React useEffect Cleanup and Event Listeners
React Beginner 1 min read

useEffect Cleanup and Event Listeners

Cleanup functions are essential when an effect attaches event listeners to the window, document, or any DOM node outside the component tree. Without cleanup, each re-render stacks up additional listeners that all fire for the same event, producing subtle bugs that are difficult to reproduce. The pattern is always the same: register inside the effect body, deregister inside the returned cleanup function. The same applies to WebSocket connections, RxJS subscriptions, and third-party library instances that expose destroy or disconnect methods. React 18 introduced Strict Mode double-invocation of effects in development to help surface exactly these cleanup issues. If your app behaves oddly in development but correctly in production, an improperly cleaned-up effect is usually the culprit. Treating every effect as if it will mount and unmount many times forces you to write robust, side-effect-free code.
Example
import { useState, useEffect } from 'react';

function WindowSize() {
    const [size, setSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight,
    });

    useEffect(() => {
        function handleResize() {
            setSize({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        }

        window.addEventListener('resize', handleResize);

        // Cleanup removes the listener on unmount / before re-run
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, []); // only runs once – no dependencies

    return (
        <p>
            Window: {size.width} × {size.height}
        </p>
    );
}

function TimerDemo() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const id = setInterval(() => setCount(c => c + 1), 1000);
        return () => clearInterval(id); // clear on unmount
    }, []);

    return <p>Seconds elapsed: {count}</p>;
}