Personal Catalogue: React & Firebase — Chapter 8, Exercise 3 ===================================================================== TASK Replace the naive hook with the fixed useDuplicateCheck (debounced, with the latestQuery staleness guard) and repeat the exact same typing sequence from Exercise 2, confirming the false warning no longer appears — then explain why this same fix also reduces this chapter's own real Firestore read cost, not just the race condition. SOLUTION import { useState, useEffect, useRef } from 'react'; import { searchItems } from '../search'; export function useDuplicateCheck(title) { const [match, setMatch] = useState(null); const latestQuery = useRef(''); useEffect(() => { const trimmed = title.trim(); latestQuery.current = trimmed; if (trimmed.length < 3) { setMatch(null); return; } const timeoutId = setTimeout(async () => { const results = await searchItems(trimmed); if (latestQuery.current !== trimmed) return; const exact = results.find((r) => r.title.toLowerCase() === trimmed.toLowerCase()); setMatch(exact || null); }, 400); return () => clearTimeout(timeoutId); }, [title]); return match; } Repeating the exact typing sequence ("Dune" then continuing to "Dune Messiah" at normal pace): no call is even made for "Dune" alone, since the 400ms debounce window is interrupted by the continued typing before it ever fires. Only after typing actually pauses on "Dune Messiah" does a single call go out — for "Dune Messiah" specifically — which correctly finds no exact match. No warning ever appears, and the "Dune" query is never even sent in this particular timing. If typing is slow enough that a "Dune" call does fire before "Messiah" is added, latestQuery.current has already moved on to "Dune Messiah" by the time that response arrives, so the check `latestQuery.current !== trimmed` discards it before setMatch() is ever called — no false warning either way. Why the same fix reduces the real read cost: without debouncing, every single keystroke that changes the title would trigger its own searchItems() call, and Chapter 6 already established that searchItems() performs a full getDocs() read of the entire items collection every time it runs — a real, metered cost under Firestore's per-document-read billing. Typing an 11-character title like "Dune Messiah" without debouncing would trigger up to 11 separate full- collection reads. With the 400ms debounce, typing at a normal pace collapses that down to at most one or two real reads — the same delay that fixes the correctness problem happens to be exactly the mechanism that avoids paying for nearly every character typed. WHY THIS WORKS AS AN ANSWER ---------------------------- It confirms the real fix eliminates the false warning by tracing the actual timing of the debounce and the staleness guard together, and it connects the fix back to Chapter 6's own already-established real Firestore cost model rather than treating debouncing as only a UX concern.