Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/components/SearchModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,10 @@ const Hit = ({
}
}

const handleClick = () => {
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return
}
closeSearch()
}

Expand Down Expand Up @@ -779,6 +782,22 @@ export function SearchModal() {
}
}, [isOpen])

React.useEffect(() => {
if (!isOpen) return

const focusSearchInput = () => {
const input = containerRef.current?.querySelector<HTMLInputElement>(
'input[type="search"]',
)
input?.focus()
}

const id = window.requestAnimationFrame(() => {
window.requestAnimationFrame(focusSearchInput)
})
return () => window.cancelAnimationFrame(id)
}, [isOpen])

Comment on lines +785 to +800
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Cleanup only cancels the outer requestAnimationFrame.

If the outer rAF has already fired before the effect cleanup runs (e.g., user closes the palette within ~1 frame of opening, or isOpen toggles quickly), the inner rAF is already scheduled and will still invoke focusSearchInput() — potentially focusing an input that's about to unmount or stealing focus after close. Track the inner id and cancel it as well.

♻️ Proposed fix
-    const id = window.requestAnimationFrame(() => {
-      window.requestAnimationFrame(focusSearchInput)
-    })
-    return () => window.cancelAnimationFrame(id)
+    let innerId = 0
+    const outerId = window.requestAnimationFrame(() => {
+      innerId = window.requestAnimationFrame(focusSearchInput)
+    })
+    return () => {
+      window.cancelAnimationFrame(outerId)
+      if (innerId) window.cancelAnimationFrame(innerId)
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/SearchModal.tsx` around lines 785 - 800, The effect that
focuses the search input schedules two nested requestAnimationFrame calls but
only cancels the outer id; update the effect (useEffect in SearchModal.tsx) to
capture both the outer and inner requestAnimationFrame ids (e.g., idOuter and
idInner) when scheduling focusSearchInput (which uses containerRef and
querySelector('input[type="search"]')) and cancel both in the cleanup via
window.cancelAnimationFrame(idOuter) and window.cancelAnimationFrame(idInner) so
the inner callback cannot run after the component closes or unmounts.

const focusedIndexRef = React.useRef(focusedIndex)

React.useEffect(() => {
Expand Down Expand Up @@ -832,6 +851,7 @@ export function SearchModal() {
className="fixed z-[1000] top-8 left-1/2 -translate-x-1/2 w-[98%] xl:w-full max-w-3xl text-left bg-white/80 dark:bg-black/80 shadow-lg rounded-lg xl:rounded-xl divide-y divide-gray-500/20 backdrop-blur-lg dark:border dark:border-white/20 outline-none"
ref={containerRef}
onKeyDown={handleKeyDown}
onOpenAutoFocus={(event) => event.preventDefault()}
>
<DialogPrimitive.Title className="sr-only">
Search TanStack docs
Expand All @@ -856,8 +876,6 @@ export function SearchModal() {
reset: 'p-1 opacity-50 hover:opacity-100',
}}
resetIconComponent={resetIconComponent}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
/>
</div>
<SearchResults focusedIndex={focusedIndex} />
Expand Down