To synchronize React with browser APIs, useEffect connects in setup and disconnects in cleanup. Window events, observers, and timers all live outside React, so a component needs an Effect to stay attached to them. The pattern is always the same: connect in setup, disconnect in cleanup.
The cleanup is what makes the connection safe. Without it, a listener or observer keeps holding the component after it leaves, and a second one stacks on the next mount.
Connect in setup, disconnect in cleanup
React renders the UI, then runs the Effect setup. The returned cleanup runs before the next setup and on unmount.
useEffect(() => {
const subscription = source.subscribe(handleChange);
return () => subscription.unsubscribe();
}, []);The subscribe and unsubscribe calls mirror each other, so the component never leaves a dangling connection. This shape applies to nearly every browser API: the cleanup undoes the setup, so React can run the pair as often as it needs without leaking. React runs the pair an extra time in development to check the symmetry.
Window events
A menu that closes on the Escape key needs a listener on window, not on any single element. A click handler on the menu cannot catch a key pressed while focus is elsewhere, so the listener must attach to window and detach when the menu unmounts.
import { useEffect } from "react";
function Menu({ onClose }) {
useEffect(() => {
const handleKey = (event) => {
if (event.key === "Escape") onClose();
};
window.addEventListener("keydown", handleKey);
return () => window.removeEventListener("keydown", handleKey);
}, [onClose]);
return <nav>Menu</nav>;
}The listener is attached while the menu is mounted and detached when it closes. Because handleKey is declared inside the Effect, the same reference is passed to both add and remove. Escape to close is also a keyboard accessibility pattern, so the listener keeps the menu usable without a mouse and stays cleaned up when the menu leaves.
Observers
An IntersectionObserver watches whether an element enters the viewport. It is a browser API that lives outside React, which makes it a perfect candidate for an Effect. The cleanup calls disconnect so the observer does not keep holding the node.
import { useEffect, useRef } from "react";
function Section({ onVisible }) {
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) onVisible();
}, { threshold: 1 });
observer.observe(ref.current);
return () => observer.disconnect();
}, [onVisible]);
return <div ref={ref}>Content</div>;
}The observer watches the element for as long as the section stays mounted. Disconnecting in the cleanup releases the reference instead of leaving a hidden observer running. A forgotten disconnect keeps firing callbacks on a node the page already removed.
One-off reads need no cleanup
Not every browser API keeps running. Reading a value once, such as localStorage or the viewport size at mount, needs no cleanup because there is nothing to undo.
What to learn next
Cleaning up timers, listeners, and subscriptions drills into each cleanup pair, and React useEffect explained covers the shared setup. When the browser API must run before the paint, a layout Effect is the tool for that job.
Rune AI
Key Insights
- Browser APIs live outside React and need an Effect to stay connected.
- Connect in setup and disconnect in the cleanup function.
- Pass the same function reference to add and remove for listeners.
- Disconnect observers and clear timers when the component leaves.
Frequently Asked Questions
Why do browser APIs need an Effect?
What is the cleanup for addEventListener?
Do I need cleanup for every browser API?
Conclusion
Synchronizing with browser APIs always follows one shape: connect in the Effect setup and disconnect in the cleanup. Match every addEventListener with removeEventListener and every observe with disconnect.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.