React
Chapter 11: Custom Hooks — Extracting and sharing stateful logic, Rules of Hooks, useFetch, useLocalStorage, useWindowDimensions, and useDebounce.
1. What is a Custom Hook?
A Custom Hook is a regular JavaScript function whose name starts with use (e.g., useFetch, useLocalStorage) and that can call other built-in React hooks like useState, useEffect, or useRef.
Custom hooks allow you to extract complex stateful logic from your components so that multiple components can share the same behavior without duplicating code or modifying the UI tree.
💡 Real-World Analogy: Modular Power Tool Attachments
Think of your component as a cordless power tool:
- Instead of building a separate drill motor, sander motor, and saw motor from scratch every time, you have plug-in attachments (Custom Hooks):
useDrill(),useSander(),useSaw(). - Any component in your app can snap on the attachment, immediately gain all the specialized mechanics (state & effects), and output its own unique woodwork (UI)!
2. The 3 Golden Rules of Hooks
1. Top-Level Only
Never call hooks inside loops, conditions (if/else), or nested functions. Hooks must execute in the exact same order on every render.
2. React Functions Only
Only call hooks from React Functional Components or from other Custom Hooks (never from regular JS helper functions).
3. 'use' Prefix Name
Always start custom hook names with use (e.g. useAuth). This signals React's ESLint plugins to enforce hook safety rules.
3. Essential Real-World Custom Hook Examples
1. useLocalStorage: Syncing State with Browser Storage
function useLocalStorage(key, initialValue) {
// 1. Lazy state initialization: read once from localStorage
const [storedValue, setStoredValue] = React.useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// 2. Wrap setter to persist changes to localStorage automatically
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// Usage in any component:
function ThemeSelector() {
const [theme, setTheme] = useLocalStorage("app_theme", "dark");
return <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>Theme: {theme}</button>;
}
2. useWindowDimensions: Live Viewport Resize Tracker
function useWindowDimensions() {
const [dimensions, setDimensions] = React.useState({
width: window.innerWidth,
height: window.innerHeight
});
React.useEffect(() => {
function handleResize() {
setDimensions({ width: window.innerWidth, height: window.innerHeight });
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return dimensions;
}
4. Hands-On Practice Challenges
Master custom hook construction, debouncers, and hook rule validation. Click "Solve Challenge" to open the code workspace!
Challenge 1: Reusable Boolean useToggle Hook
Implement a custom hook factory createCustomToggleHook(initialState) that returns getValue(), toggle(), setTrue(), and setFalse().
- Encapsulate boolean
stateinitialized withBoolean(initialState). toggle()flips state,setTrue()forces true,setFalse()forces false.- Return the methods in an object interface.
Initial: false | After toggle: true | Force false: false
createCustomToggleHook and verify all toggle operations.
Challenge 2: useDebounce Search Query Simulator
Implement a simulateDebouncedHook(initialValue, delayMs) function that tracks both raw input state and debounced values for high-frequency search bars.
- Track
currentValueanddebouncedValue. setQuery(newVal)updates and returns{ raw, debounced }.getDebouncedValue()returns the debounced string.
Updated: { raw: "react 18 hooks", debounced: "react 18 hooks" }
Challenge 3: Rules of Hooks Linter & Auditor
Write an automated rule auditor validateHookRules(functionCodeString) that verifies hooks are not enclosed within if branches or for loops and adhere to the use naming convention.
- Scan for hooks inside conditional
ifblocks. - Scan for hooks inside loops (
for,while). - Verify function name starts with
useor a capital letter (component).
Audit Bad: ["VIOLATION: Hook called inside conditional if-statement.", ...] | Audit Good: "✅ Valid Hook Architecture!"
validateHookRules and click "▶ Run Code".