Skip to main content

Events & Interaction

Hooks for DOM events, keyboard shortcuts, click-outside detection, and drag-and-drop file handling.

useEventListener

Registers/unregisters an event listener on window (default), document, or a ref'd element, with the handler read from a ref so a fresh function every render doesn't tear down and re-add it.

Window width: 0px

Resize your browser window to see it update.

const [width, setWidth] = useState(window.innerWidth);

useEventListener('resize', () => setWidth(window.innerWidth));

useClickOutside

Fires a callback on a click/tap outside every ref passed in, or on Escape. Passing both a trigger and a portaled panel as separate refs avoids the classic toggle bug where clicking the trigger to close it re-opens it.

open: false
const [open, setOpen] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);
const panelRef = useRef<HTMLDivElement>(null);

// Both the trigger and the panel are "inside" — otherwise clicking the
// trigger to close it would register as an outside click and reopen it.
// escape defaults to false as of v4 — pass it explicitly to close on Escape too.
useClickOutside([triggerRef, panelRef], () => setOpen(false), {
enabled: open,
escape: true,
});

<button ref={triggerRef}>Toggle</button>;
{
open && <div ref={panelRef}>Panel</div>;
}

useKeyPress

Binds a key combo (Escape, Enter, mod+z, ...) to a handler. 'mod' normalizes to Cmd on macOS / Ctrl elsewhere. Pairs naturally with useHistoryState for undo/redo shortcuts.

Current value: 0

Try the keyboard shortcuts too: Cmd/Ctrl+Z to undo, Cmd/Ctrl+Shift+Z to redo.

const { value, setValue, undo, redo } = useHistoryState(0);

useKeyPress('mod+z', undo, { preventDefault: true });
useKeyPress('mod+shift+z', redo, { preventDefault: true });

useFileDrop

Handles drag-and-drop file input — pairs with useFileToDataUrl to cover an upload area end to end. isDragging is tracked with an enter/leave counter so it doesn't flicker as the pointer crosses child elements.

Drag an image file here
const readAsDataUrl = useFileToDataUrl();
const { dropRef, isDragging } = useFileDrop({
accept: 'image/*',
multiple: false,
onDrop: async ([file]) => setDataUrl(await readAsDataUrl(file)),
});

<div ref={dropRef}>{isDragging ? 'Drop it!' : 'Drag an image here'}</div>;

useFileToDataUrl

Reads a File as a data URL via FileReader, wrapped in a Promise-returning function.

Pick an image file to see it read as a data URL.

const readAsDataUrl = useFileToDataUrl();
const dataUrl = await readAsDataUrl(file);