React

Verified against Claude Code · 2026-07-30

Build search-as-you-type that stays responsive without lagging the input

Implements a live-filtering search box choosing correctly between useDeferredValue, manual debouncing, or both — because they solve different problems and neither one alone covers a network-backed search.

Claude CodeCursorChatGPTClaude4 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

You are implementing a search-as-you-type input in React. You choose between useDeferredValue, manual debouncing, or a combination of both based on where the actual cost of each keystroke lives — in expensive client-side rendering, in a network request, or in both — rather than reaching for whichever one you used last time.

SEARCH BEHAVIOR
A command-palette style search over a list of 3,000 in-app actions, filtered client-side as the user types.

DATA SOURCE
Fully client-side — all 3,000 actions are loaded once at app start and kept in memory.

LATENCY BUDGET
Input must never visibly lag on any keystroke; results can lag the keystroke by up to roughly 100ms without feeling broken.

CURRENT IMPLEMENTATION
A single useState for the query, filtering the full 3,000-item array inline in the render body on every keystroke — noticeably laggy on older laptops.

SELECTION RULES
If the cost is entirely in client-side rendering — filtering or sorting a large already-loaded array on every keystroke — use useDeferredValue on the search term and render the expensive list against the deferred value, not the raw input value. This keeps the input itself always responsive because React can interrupt and deprioritize the expensive re-render triggered by the deferred value without ever blocking the actual keystroke from appearing; it does not delay when the underlying value updates, only when the expensive re-render is allowed to catch up to it. If the cost is in a network request, useDeferredValue alone does not help — it does not reduce how many requests fire, since the value itself still updates on every keystroke. Add an actual debounce on the value used to trigger the fetch, so a request only fires after typing pauses for the given interval, and cancel any in-flight request that a newer keystroke has superseded so a slow, stale response can never overwrite a faster response to a more recent query. If both a large in-memory dataset and a network refinement are involved, combine them: debounce the network trigger, and separately use useDeferredValue for the render itself so the input never stutters while either the debounce timer or the network request is pending. Show an isPending or isStale indicator during the deferred or debounced window so the user can tell displayed results might not yet reflect their latest keystroke, rather than silently presenting possibly-outdated results as current. Never wrap the input's own value in useDeferredValue or a debounce — the character the user just typed must render in the input on every keystroke with zero delay regardless of which mechanism is deferring the expensive work downstream; deferring or debouncing the input's own displayed value, rather than only the value used to drive the filter or the fetch, is what actually causes the perceptible typing lag this whole prompt exists to prevent.

OUTPUT FORMAT
1. Which mechanism or combination you chose and the specific reason tied to where the cost actually lives.
2. The implementation, as real code, including request cancellation if a network call is involved.
3. What the stale/pending indicator looks like and exactly when it appears and disappears.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

The distinction this prompt insists on — useDeferredValue changes when an expensive re-render is allowed to happen, not when the underlying value updates — is the exact point most explanations of this hook get subtly wrong, and getting it wrong leads directly to using useDeferredValue for a network-backed search and being confused when the same number of requests still fire on every keystroke, because the hook never touched the update itself, only the rendering work downstream of it. Requiring actual request cancellation for the network case targets a real, common race condition: without cancellation, a slow response to an earlier, now-irrelevant query can resolve after a faster response to the user's actual latest query, and overwrite the correct results on screen with stale ones, which is a bug that only appears under real variable network latency and is invisible in a fast local-dev environment where every request returns in roughly the same order it was sent. Requiring a visible pending or stale indicator during the deferred or debounced window matters because both mechanisms create a genuine, if brief, gap between what the user typed and what's currently displayed — silently presenting that in-between state as final, current information is a small but real trust cost, and the fix is one boolean and a bit of UI, not a structural change, so leaving it out is a choice, not a limitation. The rule against deferring or debouncing the input's own displayed value targets the exact mistake that reintroduces the lag both mechanisms are supposed to eliminate: if the value bound to the input element is itself the deferred or debounced value rather than a separate raw state updated on every keystroke, the character on screen now waits for the same deprioritized render or timer the filter results wait for, which defeats the entire premise of using either mechanism in the first place and produces the visibly laggy typing experience the prompt was written to avoid.

What you get back

const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; const results = useMemo( () => actions.filter(a => a.label.toLowerCase().includes(deferredQuery.toLowerCase())), [deferredQuery] ); // input always reflects 'query' directly — never lags <input value={query} onChange={e => setQuery(e.target.value)} /> <ResultsList results={results} className={isStale ? 'opacity-60' : ''} /> Since this case is fully client-side with no network call, no debounce or request cancellation was needed — useDeferredValue alone covers the stated latency budget by letting React deprioritize the 3,000-item filter behind every keystroke.

Verified against

Claude Code Sonnet 4.6 · 2026-07-30

ChatGPT GPT-5.1 · 2026-08-04

Changelog

  • 2026-07-30 Initial publish, verified against Claude Code (Sonnet 4.6) and ChatGPT (GPT-5.1).

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All React prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY