import { useState, useRef, useCallback } from "react"; // ============================================= // TODO: Implement the debounce function // ============================================= // // A debounce function takes a callback and a delay, and returns // a new function that: // 1. Resets a timer on every call // 2. Only invokes the callback after the caller stops calling // for `delay` milliseconds // // Signature: // function debounce<T extends (...args: any[]) => void>( // fn: T, // delay: number // ): (...args: Parameters<T>) => void // // Hints: // - Use setTimeout / clearTimeout // - Store the timer ID in a closure variable (let timer) // - Return a wrapper function that clears the old timer // and sets a new one each time it's called // - The wrapper should forward all arguments to fn function debounce(fn: (...args: any[]) => void, delay: number) { // TODO: Your implementation here // For now it just calls fn immediately (no debouncing) return (...args: any[]) => { fn(...args); }; } // ============================================= // Log entry type (DO NOT MODIFY) // ============================================= interface LogEntry { id: number; text: string; timestamp: string; type: "raw" | "debounced"; } export default function App() { const [query, setQuery] = useState(""); const [logs, setLogs] = useState<LogEntry[]>([]); const logIdRef = useRef(0); // ============================================= // Helper: adds a log entry (DO NOT MODIFY) // ============================================= const addLog = (text: string, type: LogEntry["type"]) => { logIdRef.current += 1; setLogs((prev) => [ { id: logIdRef.current, text, timestamp: new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit", fractionalSecondDigits: 3, }), type, }, ...prev, ].slice(0, 50) ); }; // ============================================= // Simulated API call — this should be debounced // ============================================= const search = (value: string) => { addLog(`API call → "${value}"`, "debounced"); }; // TODO: Wrap `search` with your debounce function (500ms delay) // Hint: use useCallback so the debounced version is stable across renders // // const debouncedSearch = useCallback(debounce(search, 500), []); const debouncedSearch = search; // ← replace this line // ============================================= // Input handler (DO NOT MODIFY) // ============================================= const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; setQuery(value); addLog(`Keystroke → "${value}"`, "raw"); debouncedSearch(value); }; const clearLogs = () => setLogs([]); return ( <div style={{ maxWidth: 600, margin: "0 auto", padding: 24, fontFamily: "system-ui" }}> <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 4 }}> Custom Debounce </h2> <p style={{ fontSize: 14, color: "#666", marginBottom: 24 }}> Implement the <code>debounce</code> function at the top of this file. Right now every keystroke fires an API call — your debounce should batch them into one call after 500ms of silence. </p> {/* Search Input */} <div style={{ marginBottom: 20 }}> <label style={{ display: "block", fontSize: 14, fontWeight: 500, marginBottom: 8 }}> Search Input </label> <input type="text" value={query} onChange={handleChange} placeholder="Start typing to see debounce in action..." style={{ width: "100%", padding: "10px 14px", border: "1px solid #d1d5db", borderRadius: 8, fontSize: 14, outline: "none", boxSizing: "border-box", }} /> </div> {/* Event Log */} <div style={{ border: "1px solid #e5e7eb", borderRadius: 8, overflow: "hidden" }}> {/* Log header */} <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "10px 14px", background: "#f9fafb", borderBottom: "1px solid #e5e7eb", fontSize: 13, }}> <span style={{ fontWeight: 600 }}>Event Log</span> <div style={{ display: "flex", alignItems: "center", gap: 16 }}> <span style={{ display: "flex", alignItems: "center", gap: 6 }}> <span style={{ width: 8, height: 8, borderRadius: "50%", background: "#d1d5db", display: "inline-block" }} /> Keystroke </span> <span style={{ display: "flex", alignItems: "center", gap: 6 }}> <span style={{ width: 8, height: 8, borderRadius: "50%", background: "#22c55e", display: "inline-block" }} /> API Call </span> <button onClick={clearLogs} style={{ color: "#999", cursor: "pointer" }}> Clear </button> </div> </div> {/* Log entries */} <div style={{ height: 300, overflowY: "auto" }}> {logs.length === 0 ? ( <div style={{ display: "flex", alignItems: "center", justifyContent: "center", height: "100%", color: "#999", fontSize: 14 }}> Type in the search box to see events </div> ) : ( logs.map((log) => ( <div key={log.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "8px 14px", borderBottom: "1px solid #f3f4f6", fontSize: 13, }} > <span style={{ width: 8, height: 8, borderRadius: "50%", background: log.type === "raw" ? "#d1d5db" : "#22c55e", flexShrink: 0, }} /> <span style={{ fontFamily: "monospace", fontSize: 11, color: "#999", flexShrink: 0 }}> {log.timestamp} </span> <span style={{ color: log.type === "raw" ? "#888" : "#111", fontWeight: log.type === "raw" ? 400 : 600, }}> {log.text} </span> </div> )) )} </div> </div> {/* Hint */} <div style={{ marginTop: 24, padding: 16, background: "#f9fafb", borderLeft: "4px solid #111", borderRadius: "0 8px 8px 0", fontSize: 13, color: "#555", }}> <strong>Expected behavior:</strong> You should see many grey "Keystroke" entries but only <strong>one</strong> green "API call" entry after you stop typing for 500ms. If you see an API call on every keystroke, your debounce isn't working yet. </div> </div> ); }