A debounced search waits until typing pauses before sending work. Every new value replaces the pending value, so the timer for older text must be canceled. Without cancellation, the final screen may look correct while the service still receives a request for every keystroke. Clearing the field is another transition: it should clear the displayed query without sending an empty request.
You'll use a short deterministic delay and a visible request count. Replace one query with another before the delay expires, then check that only the newest text is committed. The count provides evidence about canceled work without using a real service. Keep the current input separate from the committed query and include the input in the effect dependency list. The later tasks change the query text and team context.
function App(){const [query,setQuery]=useState(''),[committed,setCommitted]=useState(''),[requests,setRequests]=useState(0);useEffect(()=>{if(!query.trim()){setCommitted('');return}const timer=setTimeout(()=>{setCommitted(query);setRequests(n=>n+1)},60);return ()=>clearTimeout(timer)},[query]);return <><h1>Shipment search</h1><label>Search<input value={query} onChange={e=>setQuery(e.target.value)}/></label><output aria-label="Requests">{requests}</output><output aria-label="Committed query">{committed}</output></>}
Trace the control, state change and accessible output in the completed example.