From 5e10dc24a5a8ba53be2ebcf2807d8b4114b64cd4 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 14 Jul 2026 10:54:31 +0200 Subject: [PATCH] fix(web): keep focus in modals while typing (add-artist bug) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Modal focus-trap effect depended on [open, onClose] and called panelRef.focus() on every run. Callers that pass a fresh onClose each render (the common case, e.g. artists-client's closeAdd) re-ran the effect on every parent re-render — so typing one char into the add-artist search (setQuery → re-render → new onClose) yanked focus off the back onto the panel, making the field untypable. Hold the latest onClose in a ref and depend on [open] alone, so focus-on-open runs once per open. Fixes every Modal caller, not just add-artist. Verified live via Playwright: typing "radiohead" one key at a time keeps the value and document.activeElement on the search input. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/app/_ui/modal.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/src/app/_ui/modal.tsx b/web/src/app/_ui/modal.tsx index ae8678d..6f098ca 100644 --- a/web/src/app/_ui/modal.tsx +++ b/web/src/app/_ui/modal.tsx @@ -22,6 +22,12 @@ export function Modal({ }) { const panelRef = useRef(null); const lastFocus = useRef(null); + // Keep the latest onClose in a ref so the setup effect can depend on [open] alone. If it + // depended on onClose, a caller passing a fresh callback each render (the common case) + // would re-run the effect on every parent re-render — and panelRef.focus() below would + // yank focus off a just-typed back onto the panel (the add-artist modal bug). + const onCloseRef = useRef(onClose); + onCloseRef.current = onClose; useEffect(() => { if (!open) return; @@ -32,7 +38,7 @@ export function Modal({ function onKey(e: KeyboardEvent) { if (e.key === "Escape") { - onClose(); + onCloseRef.current(); return; } if (e.key !== "Tab") return; @@ -54,7 +60,7 @@ export function Modal({ document.body.style.overflow = prevOverflow; lastFocus.current?.focus(); }; - }, [open, onClose]); + }, [open]); if (!open || typeof document === "undefined") return null;