Custom Hooks in React with TypeScript
How I structure reusable logic with custom hooks — from useDebounce to useLocalStorage.
Why Custom Hooks
React hooks let you reuse stateful logic without changing your component hierarchy. The moment you find yourself copy-pasting useEffect or useState patterns across components, that logic belongs in a custom hook.
useDebounce
A debounce hook is one of the first custom hooks I reach for in any project. It delays updating a value until after a specified delay has passed since the last change.
import { useState, useEffect } from 'react';
export function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
Usage — search input that only fires an API call after the user stops typing:
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 400);
useEffect(() => {
if (debouncedSearch) fetchResults(debouncedSearch);
}, [debouncedSearch]);
useLocalStorage
Persisting state to localStorage is another pattern that benefits from a dedicated hook. It also handles the serialization/deserialization and errors gracefully.
import { useState, useCallback } from 'react';
export function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
try {
const stored = localStorage.getItem(key);
return stored ? (JSON.parse(stored) as T) : initial;
} catch {
return initial;
}
});
const set = useCallback(
(next: T | ((prev: T) => T)) => {
setValue((prev) => {
const resolved = typeof next === 'function' ? (next as (prev: T) => T)(prev) : next;
localStorage.setItem(key, JSON.stringify(resolved));
return resolved;
});
},
[key],
);
return [value, set] as const;
}
Type Safety Matters
The generic <T> parameter makes both hooks fully type-safe — the returned value preserves whatever type you pass in, no any casts needed.
Custom hooks like these reduce noise in components and make the intent of your code clearer. If you aren’t extracting reusable logic into hooks yet, start with the patterns above — they cover 80% of what you’ll need.