Build a search box whose suggestions come from an async source. As the user types, you want to fetch matching results — but firing a request on every keystroke is wasteful and racy. This is the classic autocomplete problem: debounce the typing, show a loading state, and make sure a slow response for an old query never overwrites a newer one.
There's no real network here — a searchAPI(query) helper returns a Promise that resolves a filtered slice of a fixed list after ~400ms, so you can focus on the timing and state logic.
The starter App.tsx renders just the input in its resting state. Make it live:
searchAPI.Loading… while a request is in flight, then render the results dropdown (or No matches).ap quickly, pause. After 300ms of quiet, one request fires; Loading… shows, then Apple, Apricot, Grape, … appears.a, then immediately ap, then app. Only one fetch runs — for app — because each keystroke restarts the timer.ap lands after you've typed app. It is discarded, so the list never flashes the wrong matches.setTimeout and starts a new one; the fetch only fires when the timer survives 300ms.searchAPI stands in for fetch. In real life you'd also AbortController-cancel the old request; here the id guard is what keeps results correct.styles.css; focus on the state and effects.