You can handle DOM events cleanly in React by wrapping them inside custom hooks. The pattern is always:
- Add the listener in
useEffect
- Remove it in the cleanup
- Expose a simple API to components
Here are the two core patterns you’ll reuse.
Keystroke hook
import { useEffect } from "react";
export function useKey(key: string, handler: () => void) {
useEffect(() => {
function onKey(e: KeyboardEvent) {
if (e.key.toLowerCase() === key.toLowerCase()) handler();
}
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [key, handler]);
}
Usage:
useKey("d", () => dispatch(doSomething()));
Resize hook
import { useState, useEffect } from "react";
export function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
function onResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return size;
}
Usage:
const { width, height } = useWindowSize();
General event hook pattern
export function useEvent<K extends keyof WindowEventMap>(
type: K,
handler: (e: WindowEventMap[K]) => void,
) {
useEffect(() => {
window.addEventListener(type, handler);
return () => window.removeEventListener(type, handler);
}, [type, handler]);
}
Usage:
useEvent("scroll", () => console.log("scrolling"));
useEvent("keydown", (e) => console.log(e.key));
How it all fits
Inside a component:
import { useKey } from "./useKey";
function MyComponent() {
const dispatch = useDispatch();
useKey("d", () => dispatch(openDebugPanel()));
useKey("Escape", () => dispatch(closeDialogs()));
return null;
}
This gives you a clean, reusable, idiomatic React pattern for attaching DOM listeners with proper cleanup.
You can handle DOM events cleanly in React by wrapping them inside custom hooks. The pattern is always:
useEffectHere are the two core patterns you’ll reuse.
Keystroke hook
Usage:
Resize hook
Usage:
General event hook pattern
Usage:
How it all fits
Inside a component:
This gives you a clean, reusable, idiomatic React pattern for attaching DOM listeners with proper cleanup.