- See Claude Opus 5.5
Write a React hook that debounces a value and cancels on unmount. Include the TypeScript types.
Claude Opus 5.5
useDebouncedValueanduseDebouncedCallbackBelow are two hooks:
useDebouncedValue: debounces a value. This is what you asked for.useDebouncedCallback: debounces a function, withcancelandflushcontrols. It's useful when you need to debounce an event handler rather than state.
Both cancel any pending timer when the component unmounts, so no state update or callback runs after unmount.
1.
useDebouncedValue// useDebouncedValue.ts import { useEffect, useState } from 'react'; /** * Returns `value` after it has stopped changing for `delay` ms. * Any pending update is cancelled when `value` or `delay` changes, * or when the component unmounts. */ export function useDebouncedValue<T>(value: T, delay: number = 300): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); useEffect(() => { const timeoutId: ReturnType<typeof setTimeout> = setTimeout(() => { setDebouncedValue(value); }, delay); // Runs before the next effect (value/delay changed) AND on unmount. return () => { clearTimeout(timeoutId); }; }, [value, delay]); return debouncedValue; }Usage
import { useEffect, useState } from 'react'; import { useDebouncedValue } from './useDebouncedValue'; interface User { id: string; name: string; } export function UserSearch() { const [query, setQuery] = useState<string>(''); const [results, setResults] = useState<User[]>([]); const debouncedQuery = useDebouncedValue(query, 400); useEffect(() => { if (!debouncedQuery) { setResults([]); return; } const controller = new AbortController(); fetch(`/api/users?q=${encodeURIComponent(debouncedQuery)}`, { signal: controller.signal, }) .then((res) => res.json() as Promise<User[]>) .then(setResults) .catch((err: unknown) => { if ((err as Error).name !== 'AbortError') console.error(err); }); return () => controller.abort(); // cancel in-flight request too }, [debouncedQuery]); return ( <> <input value={query} onChange={(e) => setQuery(e.target.value)} /> <ul> {results.map((u) => ( <li key={u.id}>{u.name}</li> ))} </ul> </> ); }2.
useDebouncedCallback(withcancel/flush)// useDebouncedCallback.ts import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'; export interface DebouncedFunction<Args extends unknown[]> { (...args: Args): void; /** Cancel any pending invocation. */ cancel: () => void; /** Immediately invoke the pending call, if any. */ flush: () => void; /** Whether a call is currently scheduled. */ isPending: () => boolean; } // useLayoutEffect warns during SSR; fall back to useEffect on the server. const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; export function useDebouncedCallback<Args extends unknown[]>( callback: (...args: Args) => void, delay: number = 300 ): DebouncedFunction<Args> { const callbackRef = useRef(callback); const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); const pendingArgsRef = useRef<Args | null>(null); // Always call the latest callback without resetting the debounce timer. useIsomorphicLayoutEffect(() => { callbackRef.current = callback; }, [callback]); const debounced = useMemo<DebouncedFunction<Args>>(() => { const invoke = (): void => { const args = pendingArgsRef.current; timeoutRef.current = null; pendingArgsRef.current = null; if (args) callbackRef.current(...args); }; const cancel = (): void => { if (timeoutRef.current !== null) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } pendingArgsRef.current = null; }; const fn = ((...args: Args): void => { pendingArgsRef.current = args; if (timeoutRef.current !== null) clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(invoke, delay); }) as DebouncedFunction<Args>; fn.cancel = cancel; fn.flush = () => { if (timeoutRef.current !== null) { clearTimeout(timeoutRef.current); invoke(); } }; fn.isPending = () => timeoutRef.current !== null; return fn; }, [delay]); // Cancel on unmount (and when `delay` changes, which creates a new function). useEffect(() => { return () => debounced.cancel(); }, [debounced]); return debounced; }Usage
import { useDebouncedCallback } from './useDebouncedCallback'; export function AutoSaveEditor({ docId }: { docId: string }) { const save = useDebouncedCallback((content: string) => { void fetch(`/api/docs/${docId}`, { method: 'PUT', body: JSON.stringify({ content }), }); }, 1000); return ( <textarea onChange={(e) => save(e.target.value)} // typed: (content: string) => void onBlur={() => save.flush()} // save immediately when leaving /> ); }Notes
ReturnType<typeof setTimeout>keeps the timer type correct in both browser (number) and Node (Timeout) environments.Cancellation on unmount comes from the effect cleanup functions. React calls them on unmount, so
clearTimeoutstops any pendingsetStateor callback.Stable callback identity:
useDebouncedCallbackstores the latestcallbackin a ref. Passing an inline arrow function therefore doesn't reset the timer on every render. The returned function only changes whendelaychanges.StrictMode double-mounts effects in development. Both hooks handle this correctly because cleanup clears the timer before the effect runs again.
Asked Nov 1, 2026. Unedited.
- See Gemini 4 Argon
Write a React hook that debounces a value and cancels on unmount. Include the TypeScript types.
Gemini 4 Argon
Here is a clean, generic
useDebouncehook written in TypeScript. It automatically handles canceling the timeout both when the value/delay changes and when the component unmounts.useDebounce.tsimport { useState, useEffect } from 'react'; /** * Custom hook that debounces a value. * * @param value The value to debounce. * @param delay The delay in milliseconds. * @returns The debounced value. */ export function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); useEffect(() => { // Set a timeout to update the debounced value after the specified delay const timer = setTimeout(() => { setDebouncedValue(value); }, delay); // Cleanup function: cancels the timeout if the value or delay changes // before the timeout completes, OR if the component unmounts. return () => { clearTimeout(timer); }; }, [value, delay]); return debouncedValue; }Example Usage
Here is how you can use it in a component (e.g., waiting for a user to stop typing before making an API call):
import React, { useState, useEffect } from 'react'; import { useDebounce } from './useDebounce'; export const SearchComponent: React.FC = () => { const [searchTerm, setSearchTerm] = useState<string>(''); // Debounce the search term so it only updates 500ms after the user stops typing const debouncedSearchTerm = useDebounce<string>(searchTerm, 500); useEffect(() => { if (debouncedSearchTerm) { console.log(`Fetching API results for: ${debouncedSearchTerm}`); // Perform your API call here } }, [debouncedSearchTerm]); return ( <input type="text" placeholder="Search..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} /> ); };Key Features:
Generics (
<T>): It preserves the exact TypeScript type of whatever value you pass into it (strings, numbers, objects, arrays, etc.).Unmount Safety: Because
clearTimeoutis returned in theuseEffectcleanup function, React guarantees it will run if the component unmounts, preventing memory leaks and "state update on an unmounted component" warnings.
Asked Nov 1, 2026. Unedited.
- See Claude Fable 5.1
Write a React hook that debounces a value and cancels on unmount. Include the TypeScript types.
Claude Fable 5.1
Here's a typed
useDebouncehook. It returns the debounced value and clears the pending timer whenever the input changes or the component unmounts, so you never get a state update on an unmounted component.import { useEffect, useState } from "react"; /** * Returns a debounced copy of `value` that only updates after `delayMs` * milliseconds have passed without `value` changing. * * The pending update is cancelled when `value` or `delayMs` changes, * and when the component unmounts. */ export function useDebounce<T>(value: T, delayMs: number = 300): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); useEffect(() => { const timeoutId: ReturnType<typeof setTimeout> = setTimeout(() => { setDebouncedValue(value); }, delayMs); // Cleanup runs before the next effect and on unmount. return () => { clearTimeout(timeoutId); }; }, [value, delayMs]); return debouncedValue; }Usage
function Search() { const [query, setQuery] = useState(""); const debouncedQuery = useDebounce(query, 400); useEffect(() => { if (!debouncedQuery) return; // Fire the request only after the user pauses typing for 400ms. fetch(`/api/search?q=${encodeURIComponent(debouncedQuery)}`); }, [debouncedQuery]); return ( <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search…" /> ); }Notes
Generic
Tmeans it works for strings, numbers, objects, etc., with full type inference at the call site.ReturnType<typeof setTimeout>keeps the timer ID type correct in both browser (number) and Node (Timeout) environments.Cancellation on unmount comes for free from the effect cleanup — React calls it when the component unmounts, so
setDebouncedValueis never invoked afterward.If you need an immediate update when
delayMsis0, you can short-circuit at the top of the effect withif (delayMs <= 0) { setDebouncedValue(value); return; }.
Asked Nov 1, 2026. Unedited.
- See Claude Sonnet 5.5
Write a React hook that debounces a value and cancels on unmount. Include the TypeScript types.
Claude Sonnet 5.5
useDebouncehook// useDebounce.ts import { useEffect, useState } from "react"; /** * Returns a debounced copy of `value` that only updates after `delay` ms * have passed without `value` changing. * * The pending timer is cleared when `value` or `delay` changes, * and when the component unmounts. */ export function useDebounce<T>(value: T, delay: number = 300): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); useEffect(() => { const timeoutId: ReturnType<typeof setTimeout> = setTimeout(() => { setDebouncedValue(value); }, delay); // Runs before the next effect and on unmount return () => clearTimeout(timeoutId); }, [value, delay]); return debouncedValue; }Usage
import { useEffect, useState } from "react"; import { useDebounce } from "./useDebounce"; export function Search() { const [query, setQuery] = useState(""); const debouncedQuery = useDebounce(query, 500); useEffect(() => { if (!debouncedQuery) return; const controller = new AbortController(); fetch(`/api/search?q=${encodeURIComponent(debouncedQuery)}`, { signal: controller.signal, }).catch(() => {}); return () => controller.abort(); }, [debouncedQuery]); return ( <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search…" /> ); }Why this cancels correctly
The effect's cleanup function calls
clearTimeout. React runs it before re-running the effect (so each new value restarts the timer) and on unmount (sosetDebouncedValuenever fires on an unmounted component).ReturnType<typeof setTimeout>keeps the timer type correct in both browser (number) and Node (NodeJS.Timeout) typings.
Optional: version with
cancelandflushIf you need manual control (e.g. flush on Enter, cancel on Escape):
// useDebouncedValue.ts import { useCallback, useEffect, useRef, useState } from "react"; export interface DebounceControls { /** Cancel the pending update, if any. */ cancel: () => void; /** Apply the latest value immediately and cancel the pending timer. */ flush: () => void; } export function useDebouncedValue<T>( value: T, delay: number = 300 ): [T, DebounceControls] { const [debounced, setDebounced] = useState<T>(value); const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const latestValue = useRef<T>(value); latestValue.current = value; const cancel = useCallback(() => { if (timerRef.current !== null) { clearTimeout(timerRef.current); timerRef.current = null; } }, []); const flush = useCallback(() => { cancel(); setDebounced(latestValue.current); }, [cancel]); useEffect(() => { cancel(); timerRef.current = setTimeout(() => { timerRef.current = null; setDebounced(latestValue.current); }, delay); return cancel; // cancels on change and on unmount }, [value, delay, cancel]); return [debounced, { cancel, flush }]; }const [debounced, { flush, cancel }] = useDebouncedValue(query, 500); <input value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") flush(); if (e.key === "Escape") cancel(); }} />Asked Nov 1, 2026. Unedited.
- See MiMo V2.6 Pro
Write a React hook that debounces a value and cancels on unmount. Include the TypeScript types.
MiMo V2.6 Pro
import { useEffect, useState, useRef } from 'react'; /** * Debounces a value so it only updates after a specified delay of * no further changes. The pending timer is cancelled on unmount. * * @template T The type of the value being debounced. * @param value The value to debounce. * @param delayMs Delay in milliseconds (default: 300). * @returns The debounced value (updates only after `delayMs` of silence). */ function useDebouncedValue<T>(value: T, delayMs: number = 300): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null); useEffect(() => { // Cancel any pending timeout when value changes or on unmount if (timerRef.current !== null) { clearTimeout(timerRef.current); } timerRef.current = setTimeout(() => { setDebouncedValue(value); }, delayMs); // Cleanup runs on every re-run (value change) AND on unmount return () => { if (timerRef.current !== null) { clearTimeout(timerRef.current); } }; }, [value, delayMs]); return debouncedValue; } export default useDebouncedValue;How it works
Concern Mechanism Debounce Every time valuechanges, a newsetTimeoutis scheduled. The state is only updated afterdelayMshas elapsed with no further changes.Cancel on unmount The useEffectcleanup function clears the pending timeout. This cleanup runs on every dependency change and when the component unmounts.Cancel on rapid changes Because the previous timer is cleared before creating a new one, rapid successive changes never fire stale updates. Type safety The generic Tpropagates from the input value to the returned value — no casting needed.Usage example
import { useState } from 'react'; import useDebouncedValue from './useDebouncedValue'; function SearchBar() { const [query, setQuery] = useState(''); const debouncedQuery = useDebouncedValue(query, 500); // Only runs when the user pauses typing for 500 ms useEffect(() => { if (!debouncedQuery) return; fetchResults(debouncedQuery); }, [debouncedQuery]); return ( <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search…" /> ); }Optional: debounce a callback instead
If you need to debounce a function call rather than a value (e.g. an event handler), here is a companion hook that also cancels on unmount:
import { useEffect, useRef, useCallback } from 'react'; function useDebouncedCallback<A extends unknown[]>( callback: (...args: A) => void, delayMs: number = 300 ): (...args: A) => void { const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const callbackRef = useRef(callback); // Keep the latest callback without re-creating the debounce timer useEffect(() => { callbackRef.current = callback; }, [callback]); // Cancel pending call on unmount useEffect(() => { return () => { if (timerRef.current !== null) { clearTimeout(timerRef.current); } }; }, []); return useCallback( (...args: A) => { if (timerRef.current !== null) { clearTimeout(timerRef.current); } timerRef.current = setTimeout(() => { callbackRef.current(...args); }, delayMs); }, [delayMs] ); }This variant uses
callbackRefto always invoke the latest version of the callback while keeping the debounce timer stable, and tears down the timer in a dedicated unmount cleanup.Asked Nov 1, 2026. Unedited.
Top 10 for coding
Scores from artificialanalysis.ai and lmarena.ai.
API price is per 1M tokens, in / out. Updated Nov 2, 2026.
| # | Model | Artificial Analysis | LMArena coding | API price |
|---|---|---|---|---|
| 1 | Claude Opus 5.5 | 57.6 | 1,545 | $4 / $20 |
| 2 | Gemini 4 Argon | — | — | $2 / $10 |
| 3 | Claude Fable 5.1 | 53.4 | 1,531 | $10 / $50 |
| 4 | Claude Sonnet 5.5 | 56.0 | 1,510 | $2 / $10 |
| 5 | MiMo V2.6 Pro | 46.3 | 1,533 | $0.43 / $0.87 |
| 6 | GPT-6 Astra | 52.7 | — | $10 / $50 |
| 7 | Muse Spark 1.3 | 48.1 | 1,511 | $1.25 / $4.25 |
| 8 | GPT-6.1 Sol | 51.8 | 1,482 | $2 / $10 |
| 9 | ChatGPT 5.6 Sol | 47.0 | 1,496 | $4 / $20 |
| 10 | Qwen 3.8 Max | 45.4 | 1,503 | $2 / $6 |
We rank every model per task, blending public leaderboards with our own blind tests where we have them. Price and speed don’t count toward the rank. How we rank