Custom Hooks
A custom hook is any function whose name starts with use that calls other hooks. It’s how teams extract reusable stateful logic without HOCs or render-props.
Three production-grade custom hooks
EXAMPLE
import { useState, useEffect, useRef, useCallback } from 'react';
// 1) useDebounce — delays a value until typing pauses
export function useDebounce(value, ms = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const t = setTimeout(() => setDebounced(value), ms);
return () => clearTimeout(t);
}, [value, ms]);
return debounced;
}
// 2) useLocalStorage — synced state across tabs
export 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]);
useEffect(() => {
function onStorage(e) {
if (e.key === key) setValue(JSON.parse(e.newValue));
}
window.addEventListener('storage', onStorage);
return () => window.removeEventListener('storage', onStorage);
}, [key]);
return [value, setValue];
}
// 3) useFetch — async data with abort on dep change / unmount
export function useFetch(url) {
const [state, setState] = useState({ data: null, error: null, loading: true });
useEffect(() => {
const ctrl = new AbortController();
setState(s => ({ ...s, loading: true }));
fetch(url, { signal: ctrl.signal })
.then(r => r.json())
.then(data => setState({ data, error: null, loading: false }))
.catch(err => {
if (err.name !== 'AbortError') setState({ data: null, error: err, loading: false });
});
return () => ctrl.abort();
}, [url]);
return state;
}
Why it matters
Three rules for custom hooks: 1) name starts with use (so the linter checks dep arrays); 2) they obey the same rules of hooks; 3) ideally pure, with no global side effects on first call.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
return [on, () => setOn(o => !o)];
}
Try it Yourself »
Discussion
Loading…