← arena
Nothing wrong with it
PrincipalTypeScriptReactNext.js
UX-330the ticket
Debounce the project search box
The search input fires a request on every keystroke. Debounce it to ~250ms and make sure a slow response from an old query can't overwrite the results of a newer one.
Clear the results when the box is emptied.
the agent’s descriptionagent · opus-class model
Debounced with a 250ms timer inside the effect, and used an AbortController so an in-flight request is cancelled the moment the query changes — that also removes the out-of-order response problem, since a superseded request can never resolve. The cleanup clears the timer and aborts on every change and on unmount.
Empty query short-circuits to an empty result list without hitting the network.
Written by the agent that opened the PR. Fluent, specific, and not evidence of anything.
src/components/project-search.tsx65 lines
1
"use client";2
3
import { useEffect, useState } from "react";4
5
type Result = { id: string; title: string };6
7
export function ProjectSearch() {8
const [query, setQuery] = useState("");9
const [results, setResults] = useState<Result[]>([]);10
const [pending, setPending] = useState(false);11
12
useEffect(() => {13
const trimmed = query.trim();14
15
if (trimmed === "") {16
setResults([]);17
setPending(false);18
return;19
}20
21
const controller = new AbortController();22
setPending(true);23
24
const timer = setTimeout(() => {25
fetch("/api/search?q=" + encodeURIComponent(trimmed), {26
signal: controller.signal,27
})28
.then((res) => {29
if (!res.ok) throw new Error("search failed: " + res.status);30
return res.json();31
})32
.then((data: { results: Result[] }) => {33
setResults(data.results);34
setPending(false);35
})36
.catch((err: Error) => {37
if (err.name === "AbortError") return;38
setResults([]);39
setPending(false);40
});41
}, 250);42
43
return () => {44
clearTimeout(timer);45
controller.abort();46
};47
}, [query]);48
49
return (50
<div>51
<input52
value={query}53
onChange={(event) => setQuery(event.target.value)}54
placeholder="Search projects"55
aria-label="Search projects"56
/>57
{pending ? <p role="status">Searching...</p> : null}58
<ul>59
{results.map((result) => (60
<li key={result.id}>{result.title}</li>61
))}62
</ul>63
</div>64
);65
}