Hai vấn đề tách bạch: giảm số request và race condition.
Debounce: chỉ gọi API sau khi người dùng ngừng gõ ~300ms. Đặt setTimeout và clearTimeout mỗi lần onChange, hoặc dùng một hook useDebouncedValue. Việc này cắt bớt request thừa nhưng KHÔNG giải quyết race.
Race condition (stale response): dù đã debounce, khi mạng chậm, request cho từ khoá "reac" có thể trả về SAU request "react" và ghi đè kết quả đúng. Cách xử lý:
AbortController: mỗi lần gõ mới, gọicontroller.abort()để huỷ request đang chạy, rồi tạo controller mới và truyềnsignalvàofetch. Request cũ bị reject với lỗiAbortError(nhớ bắt và bỏ qua nó, đừng hiển thị như lỗi thật).- Hoặc so khớp phiên: lưu từ khoá của request mới nhất; khi response về, chỉ nhận nó nếu từ khoá còn khớp với input hiện tại — response cũ bị loại.
js
useEffect(() => {
const controller = new AbortController()
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(r => r.json()).then(setResults)
.catch(e => { if (e.name !== 'AbortError') setError(e) })
return () => controller.abort()
}, [query])Dùng cả hai: debounce để tiết kiệm, abort/so khớp để đảm bảo đúng đắn.
Thư viện data-fetching (React Query, SWR) tránh được lỗi này sẵn vì mỗi từ khoá là một cache key riêng — response cũ rơi vào key cũ, không ghi đè key hiện tại.