044
LVL 02 — CIRCUIT BREAKER SESSION 044 DAY 44

CUSTOM HOOKS

🎫 PIXELCRAFT-032
🔧 Refactor | 🟡 Medium | Priority: 🟡 Medium

localStorage pattern duplicated in 5 components. Debounce pattern in 4 places. Keyboard shortcut setup in 3 places. Extract reusable custom hooks.
CONCEPTS.UNLOCKED
🪝
Custom Hooks
Reusable stateful logic. useLocalStorage, useDebounce, useKeyboardShortcuts — extract patterns into functions that start with "use". Share logic, not UI.
📏
Hook Rules
Only call hooks at the top level (never in loops, conditions, or nested functions). Only call in React components or custom hooks. These rules ensure consistent hook ordering.
🔗
Composing Hooks
Custom hooks can use other hooks. useLocalStorage uses useState + useEffect internally. Hooks compose just like functions compose — build complex behavior from simple parts.
When to Extract
When you see the same useState + useEffect pattern in 3+ components, extract a hook. If it's only in 1-2 places, keep it inline. Don't over-abstract too early.
Debouncing
Wait for the user to stop before acting. Slider moves 50 times per second but the filter only applies 300ms after the last move. Reduces unnecessary work.
🧩
Abstraction Layers
Each hook hides complexity behind a simple interface. useLocalStorage handles serialization, error recovery, and sync — the consumer just uses it like useState.
HANDS-ON.TASKS
01
Build useLocalStorage
function useLocalStorage(key, initialValue) { const [value, setValue] = useState(() => { try { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initialValue; } catch { return initialValue; } }); useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; } // Usage: const [theme, setTheme] = useLocalStorage('theme', 'dark');
The lazy initializer (() => ...) runs only on first render — avoids reading localStorage on every re-render. The hook returns [value, setValue] just like useState. Drop-in replacement.
02
Build useDebounce
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: slider moves → debounced value // updates 300ms after you stop
Every time value changes, the old timer is cleared (cleanup!) and a new one starts. Only when the user pauses for 300ms does the debounced value actually update — triggering the expensive filter reapplication.
03
Build useKeyboardShortcuts
function useKeyboardShortcuts(shortcuts) { useEffect(() => { const handler = (e) => { for (const [combo, action] of Object.entries(shortcuts)) { if (matchesCombo(e, combo)) { e.preventDefault(); action(); } } }; window.addEventListener('keydown', handler); return () => window.removeEventListener( 'keydown', handler ); }, [shortcuts]); }
Pass an object mapping key combos to actions: { 'ctrl+z': undo, 'ctrl+y': redo, 'ctrl+s': save }. The hook manages the listener and cleanup. Used in 3 components, defined once.
04
Refactor PixelCraft Components

Replace duplicated patterns across 5+ components:

BeforeAfterSaved
localStorage in 5 componentsuseLocalStorage~80 lines
Debounce in 4 componentsuseDebounce~60 lines
Keyboard in 3 componentsuseKeyboardShortcuts~45 lines
05
Close the Ticket
git switch -c refactor/PIXELCRAFT-032-custom-hooks git add src/hooks/ src/components/ git commit -m "Extract useLocalStorage, useDebounce, useKeyboardShortcuts (PIXELCRAFT-032)" git push origin refactor/PIXELCRAFT-032-custom-hooks # PR → Review → Merge → Close ticket ✅
CS.DEEP-DIVE

Abstraction layers.

Each custom hook hides complexity behind a simple interface. The ability to identify the right level of abstraction is what separates good engineers from great ones.

// Abstraction is everywhere:

Operating systems  → abstract hardware
Databases          → abstract storage
APIs               → abstract backend logic
Custom hooks       → abstract stateful patterns

// useLocalStorage handles serialization,
// error recovery, and sync — the consumer
// just uses it like useState.

// Change the hook → every consumer benefits.
"Hook Library"
[A] Build useWindowSize() that tracks the browser's innerWidth and innerHeight reactively. Use it to switch PixelCraft between mobile and desktop layouts.
[B] Build useUndoState(initialValue) that wraps useState with undo/redo capability. Returns [value, setValue, undo, redo, canUndo, canRedo]. Compose the Stack from Session 34.
[C] Explore usehooks.com — a curated collection of community hooks. Pick 3 that would improve PixelCraft and explain why.
REF.MATERIAL
ARTICLE
React Team
The official guide: when to extract hooks, naming conventions, sharing stateful logic, and the rules of hooks.
CUSTOM HOOKSOFFICIALESSENTIAL
VIDEO
Web Dev Simplified
Building custom hooks step by step: useLocalStorage, useToggle, useDebounce, and when extraction makes sense.
CUSTOM HOOKSPRACTICAL
ARTICLE
React Team
Why hooks must be called at the top level, why order matters, and what happens if you break the rules.
HOOKSRULESOFFICIAL
TOOL
usehooks.com
Curated collection of production-ready custom hooks: useLocalStorage, useMediaQuery, usePrevious, useEventListener, and dozens more.
HOOKSLIBRARYCOMMUNITY
VIDEO
Fireship
Overview of all major React hooks: useState, useEffect, useRef, useContext, useMemo, useCallback, useReducer, and custom hooks.
HOOKSOVERVIEW
// LEAVE EXCITED BECAUSE
useLocalStorage replaced 100+ lines of duplicated code across the app with one clean hook. Change the hook → every consumer benefits. This is engineering.