Custom Hooks
Extract and reuse stateful logic by building your own hooks
Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.
UpgradeKnowledge Debt detected
You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.
Explanation
s are functions that start with use and can call other hooks. They extract and reuse stateful logic between components.
Pattern: take any stateful logic from a component and move it into a useX function.
// Before — logic inside component
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => { setUser(data); setLoading(false); })
.catch(err => { setError(err); setLoading(false); });
}, [userId]);
// ...
}// After — extracted into custom hook
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => { setUser(data); setLoading(false); })
.catch(err => { setError(err); setLoading(false); });
}, [userId]);
return { user, loading, error };
}
// Now any component can use it:
function UserProfile({ userId }) {
const { user, loading, error } = useUser(userId);
// ...
}Rules: custom hooks must follow the Rules of Hooks (call order must be stable). The use prefix signals this to React's linter.
Examples
useLocalStorage hook
Custom hooks can wrap browser APIs to make them feel like React state
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const stored = localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
} catch {
return initialValue;
}
});
const setAndStore = (newValue) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
};
return [value, setAndStore];
}
// Usage — works like useState but persists across page loads
const [theme, setTheme] = useLocalStorage('theme', 'dark');useDebounce hook
useDebounce prevents excessive API calls by delaying until input settles
function useDebounce(value, delayMs) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delayMs);
return () => clearTimeout(timer);
}, [value, delayMs]);
return debounced;
}
// Usage — search fires only after 300ms of no typing
function Search() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => { if (debouncedQuery) fetchResults(debouncedQuery); }, [debouncedQuery]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}How well did you understand this?
Next in React
useMemo & useCallback