# React Patterns Quick Reference ## Hook Rules 1. Only call hooks at the **top level** (not inside loops, conditions, or nested functions) 2. Only call hooks from **React function components** or **custom hooks** 3. Custom hooks **must** start with `use` 4. Hook call order must be **identical** on every render ```typescript // BAD if (isLoggedIn) { const [user, setUser] = useState(null); // Conditional hook call } // GOOD const [user, setUser] = useState(null); // Use the state conditionally instead ``` --- ## Custom Hook Recipes ### useLocalStorage ```typescript function useLocalStorage(key: string, initialValue: T) { const [value, setValue] = useState(() => { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch { return initialValue; } }); useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue] as const; } // Usage const [theme, setTheme] = useLocalStorage("theme", "dark"); ``` ### useDebounce ```typescript function useDebounce(value: T, delay: number): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } // Usage const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search, 300); useEffect(() => { fetchResults(debouncedSearch); }, [debouncedSearch]); ``` ### useMediaQuery ```typescript function useMediaQuery(query: string): boolean { const [matches, setMatches] = useState( () => window.matchMedia(query).matches ); useEffect(() => { const mql = window.matchMedia(query); const handler = (e: MediaQueryListEvent) => setMatches(e.matches); mql.addEventListener("change", handler); return () => mql.removeEventListener("change", handler); }, [query]); return matches; } // Usage const isMobile = useMediaQuery("(max-width: 768px)"); ``` ### usePrevious ```typescript function usePrevious(value: T): T | undefined { const ref = useRef(undefined); useEffect(() => { ref.current = value; }); return ref.current; } // Usage const prevCount = usePrevious(count); ``` --- ## Compound Component Pattern ```typescript // Components that work together sharing implicit state const Accordion = ({ children }: { children: React.ReactNode }) => { const [openIndex, setOpenIndex] = useState(null); return (
{children}
); }; const AccordionItem = ({ index, title, children }: { index: number; title: string; children: React.ReactNode; }) => { const { openIndex, setOpenIndex } = useContext(AccordionContext); const isOpen = openIndex === index; return (
{isOpen &&
{children}
}
); }; Accordion.Item = AccordionItem; // Usage - clean, declarative API Content 1 Content 2 ``` --- ## Context Patterns ### Split State and Dispatch ```typescript // Prevent unnecessary re-renders by splitting contexts const StateContext = createContext(null); const DispatchContext = createContext(null); function Provider({ children }: { children: React.ReactNode }) { const [state, dispatch] = useReducer(reducer, initialState); return ( {children} ); } // Components that only dispatch won't re-render on state changes function useAppDispatch() { const ctx = useContext(DispatchContext); if (!ctx) throw new Error("Must be used within Provider"); return ctx; } ``` --- ## Performance Checklist ### When to Memoize | Situation | Solution | |-----------|----------| | Expensive computation | `useMemo(() => compute(data), [data])` | | Stable callback for child | `useCallback(fn, [deps])` | | Prevent child re-render | `React.memo(Component)` | | Stable object/array prop | `useMemo(() => ({ ... }), [deps])` | ### When NOT to Memoize - Simple/cheap calculations - Primitives (strings, numbers, booleans) -- already stable - Functions only used in the same component - Components that are cheap to render ### Key Optimizations ```typescript // 1. Move state down (colocate state with where it's used) // BAD: parent re-renders everything function Parent() { const [hover, setHover] = useState(false); return <>; } // GOOD: isolate the stateful part function Parent() { return <>; } function HoverSection() { const [hover, setHover] = useState(false); return ; } // 2. Pass children to avoid re-rendering them function ScrollTracker({ children }: { children: React.ReactNode }) { const [scroll, setScroll] = useState(0); // children are created by parent, not re-created on scroll change return
setScroll(e.currentTarget.scrollTop)}>{children}
; } // 3. Use key to reset component state
// 4. Lazy load heavy components const HeavyChart = lazy(() => import("./HeavyChart")); }> ``` ### React DevTools Profiler Checklist 1. Enable "Highlight updates" to spot unnecessary re-renders 2. Record a profiler session and look for: - Components re-rendering without visible changes - Long render times (>16ms blocks frames) - Cascading re-renders from context changes 3. Fix with: state colocation, memo, context splitting, or external state