How to Use AbortController for Cleaner Event Listener Cleanup in React
Normally, React developers clean up event listeners by manually calling removeEventListener in a useEffect cleanup. But there’s a cleaner, more modern way: using AbortController to automatically handle cleanup without explicit unbinding. Traditional Event Listener Setup Here’s the classic pattern you’re probably using already: useEffect(() => { const handleEsc = (e) => { if (e.key === "Escape") { onClose(); } }; window.addEventListener("keydown", handleEsc); return () => { window.removeEventListener("keydown", handleEsc); }; }, []); Cleaner Alternative Using AbortController Here’s how to refactor it to use an AbortController instead: useEffect(() => { const controller = new AbortController(); const { signal } = controller; const handleEsc = (e) => { if (e.key === "Escape") { onClose(); } }; window.addEventListener("keydown", handleEsc, { signal }); return () => controller.abort(); }, []); How It Works When you pass the signal to addEventListener, the browser automatically removes the listener when controller.abort() is called during the effect's cleanup. This reduces boilerplate and risk of bugs from missing cleanups. Pros and Cons ✅ Pros Automatic, cleaner cleanup without manual unbinding Reduced chance of memory leaks Better readability and modern browser support ⚠️ Cons Requires relatively modern browsers (Chrome 88+, Firefox 85+) Can seem unfamiliar to some teams at first
Normally, React developers clean up event listeners by manually calling removeEventListener
in a useEffect
cleanup. But there’s a cleaner, more modern way: using AbortController to automatically handle cleanup without explicit unbinding.
Traditional Event Listener Setup
Here’s the classic pattern you’re probably using already:
useEffect(() => {
const handleEsc = (e) => {
if (e.key === "Escape") {
onClose();
}
};
window.addEventListener("keydown", handleEsc);
return () => {
window.removeEventListener("keydown", handleEsc);
};
}, []);
Cleaner Alternative Using AbortController
Here’s how to refactor it to use an AbortController instead:
useEffect(() => {
const controller = new AbortController();
const { signal } = controller;
const handleEsc = (e) => {
if (e.key === "Escape") {
onClose();
}
};
window.addEventListener("keydown", handleEsc, { signal });
return () => controller.abort();
}, []);
How It Works
When you pass the signal
to addEventListener
, the browser automatically removes the listener when controller.abort()
is called during the effect's cleanup. This reduces boilerplate and risk of bugs from missing cleanups.
Pros and Cons
✅ Pros
- Automatic, cleaner cleanup without manual unbinding
- Reduced chance of memory leaks
- Better readability and modern browser support
⚠️ Cons
- Requires relatively modern browsers (Chrome 88+, Firefox 85+)
- Can seem unfamiliar to some teams at first