Set Up On Mount, Tear Down On Cleanup
Subscriptions, Listeners, Intervals
Anything you start in an effect — a timer, a listener, a subscription — must be undone in the cleanup, or you leak it.
What you'll learn
- Set up a global event listener
- Use intervals and timeouts safely
- Subscribe to an external source
Anything that “starts” something — a listener, a timer, a subscription — needs to be torn down on cleanup. Same pattern, different APIs.
Window Event Listener
function useKeyDown(target, handler) {
useEffect(() => {
function onKey(e) {
if (e.key === target) handler(e);
}
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [target, handler]);
}
// usage:
useKeyDown("Escape", closeModal); Resize / Scroll
function useWindowWidth() {
const [w, setW] = useState(window.innerWidth);
useEffect(() => {
function onResize() { setW(window.innerWidth); }
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return w;
} Intervals and Timeouts
function Clock() {
const [now, setNow] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setNow(new Date()), 1000);
return () => clearInterval(id);
}, []);
return <p>{now.toLocaleTimeString()}</p>;
} Websockets
function useFeed(url) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const ws = new WebSocket(url);
ws.onmessage = e => setMessages(prev => [...prev, e.data]);
return () => ws.close();
}, [url]);
return messages;
} IntersectionObserver / ResizeObserver
function LazyImage({ src }) {
const imgRef = useRef(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const io = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) setVisible(true);
});
if (imgRef.current) io.observe(imgRef.current);
return () => io.disconnect();
}, []);
return <img ref={imgRef} src={visible ? src : undefined} alt="" />;
} The pattern is always the same: start something, return the stop.
Custom Hook For Reuse
If you find yourself writing the same effect in two places, extract
a custom hook (chapter 5). Subscriptions are prime custom-hook
material — useMediaQuery, useOnlineStatus, useInterval,
useEventListener are all examples you’ll meet in the wild.
Up Next
The [] dep array means “only on mount”. What does that really
mean in practice?