Cleanup functions
Some effects start something that must later be stopped: an interval keeps ticking, a subscription keeps listening, a websocket stays open. If the component unmounts or the dependencies change, the old work must be shut down. Otherwise it leaks: the timer or subscription keeps consuming memory and CPU for a component that no longer exists, and if its callback calls a state setter, React warns about updating an unmounted component.
The contract: whatever the effect returns is its cleanup function.
function ChatRoom({ room }) { useEffect(() => { const conn = connect(room); // start return () => conn.close(); // stop }, [room]); return <h1>Room: {room}</h1>; }
React runs cleanup at two moments:
- Before re-running the effect because a dependency changed, old room closes before the new one opens.
- At unmount, when the component leaves the screen.
So switching from room general to room random produces: subscribe general → unsubscribe general → subscribe random.
Code exercise · javascript
The cleanup contract in plain JavaScript. effect(room) starts a subscription and returns its own shutdown function, exactly like useEffect. Run it and match the output to: mount, dependency change, unmount.
Code exercise · javascript
Your turn. You get one dep value per render: ["a", "b", "b", "c"]. Complete the changed check so the effect only re-runs when the dep actually differs from the previous render, using Object.is like React does. The repeated "b" must be skipped.
Quiz
Spot the bug: ```jsx useEffect(() => { const id = setInterval(tick, 1000); }, []); ```