Custom Hooks
Custom hooks extract reusable logic from components into standalone functions. Any function starting with use that calls other hooks is a custom hook. They are the primary pattern for sharing stateful logic in React.
1 Essential Custom Hooks
React — Custom Hooks
// useFetch — generic data fetching
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(r => r.json())
.then(d => { if (!cancelled) { setData(d); setLoading(false); } })
.catch(e => { if (!cancelled) { setError(e.message); setLoading(false); } });
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// useLocalStorage — persist state in localStorage
function useLocalStorage(key, initial) {
const [value, setValue] = useState(() => {
try { return JSON.parse(localStorage.getItem(key)) ?? initial; }
catch { return initial; }
});
useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]);
return [value, setValue];
}
// useDebounce — delay value updates
function useDebounce(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
// Usage
function SearchPage() {
const [query, setQuery] = useLocalStorage("search", "");
const debouncedQuery = useDebounce(query, 400);
const { data, loading, error } = useFetch(`/api/search?q=${debouncedQuery}`);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
{loading && <Spinner />}
{error && <p>Error: {error}</p>}
{data && <ResultsList results={data} />}
</>
);
}
2 Code Challenge
Challenge: Build a
useWindowSize hook that returns { width, height } and updates on window resize. Use it to conditionally render a mobile nav vs. desktop nav.