useDebounce hook
A small generic hook for debouncing a fast-changing value — search inputs, resize handlers, anything you don't want firing on every keystroke.
The one hook I copy into nearly every project. Debounces a value so downstream effects (a search request, a save-to-localStorage call) only fire once the value has settled.
import { useEffect, useState } from "react";
export function useDebounce<T>(value: T, delayMs = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timeout = setTimeout(() => setDebounced(value), delayMs);
return () => clearTimeout(timeout);
}, [value, delayMs]);
return debounced;
}Usage #
function SearchInput() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) runSearch(debouncedQuery);
}, [debouncedQuery]);
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}The cleanup function matters here — without clearTimeout on unmount/re-run, a stale timeout from a previous keystroke can still fire and overwrite a newer value.
Related content
Using Framer Alongside a Development Workflow, Not Instead of One
TIL that building the Clonify Framer plugin taught me Framer is a genuinely different target than a normal web build — its plugin runtime and code-component model don't map 1:1 onto React conventions.
useOptimistic auto-reverts on failure — a hand-rolled optimistic hook doesn't
TIL why React's useOptimistic hook doesn't need explicit rollback code when a server call fails, while a manual optimistic-update hook built on plain useState does.
Turning Figma Designs Into Reusable React Components
TIL that building each screen straight from its Figma frame produced pixel-accurate but unreusable components — the fix was designing the component API from the design system's tokens, not from any one screen.