Navigate
Search topics, hooks, and fundamentals
What if you are writing the same stateful logic over and over?
π‘ Custom hooks let you extract and share stateful logic. They're the composition pattern of React.
Two components with identical toggle logic. Copy-paste works, but it doesn't scale.
DarkMode Toggle
useState + toggle logic
Modal Toggle
same useState + toggle logic!
Pull the shared logic into a function that starts with "use". Now both components get it in one line.
DarkMode Toggle
useToggle(false)
Modal Toggle
useToggle(false)
Both use the same hook β one line each. Add features to the hook, all consumers get them!
// Custom Hook β extract once, use everywhere!
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return [value, toggle, setTrue, setFalse] as const;
}
// Component 1:
const [darkMode, toggleDark] = useToggle(false); // 1 line!
// Component 2:
const [modal, toggleModal] = useToggle(false); // 1 line!Name starts with "use"
React uses this convention to enforce the Rules of Hooks. If it starts with use, React knows to check hook rules.
useToggle, useDebounce, useLocalStorageCan call other hooks inside
Custom hooks are just functions that compose built-in hooks. useState, useEffect, useRef β mix and match.
useState + useEffect = useDebounceCustom hooks compose built-in hooks into reusable pieces. Here are three classics you'll use in real projects.
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
// Usage:
const darkMode = useToggle(false);
const notifications = useToggle(true);
const sound = useToggle(true);Type fast β the debounced value waits until you stop.
Raw (instant)
...
Debounced (500ms)
...
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer); // cleanup on value change
}, [value, delay]);
return debouncedValue;
}
// Usage:
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 500);
// debouncedQuery updates 500ms after you stop typingChanges are saved to localStorage. Refresh the page β your data persists!
localStorage["demo-name"] = ""
localStorage["demo-theme"] = "light"
function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initial;
} catch {
return initial;
}
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
// Usage β persists across page refreshes!
const [name, setName] = useLocalStorage("user-name", "");
const [theme, setTheme] = useLocalStorage("theme", "light");