React Best Practices
Pair with TypeScript
When working with React, always load both this skill and typescript-best-practices together. TypeScript patterns (type-first development, discriminated unions, Zod validation) apply to React code.
Core Principle: Effects Are Escape Hatches
Effects let you "step outside" React to synchronize with external systems. Most component logic should NOT use Effects. Before writing an Effect, ask: "Is there a way to do this without an Effect?"
Decision Tree
-
Need to respond to user interaction? Use event handler
-
Need computed value from props/state? Calculate during render
-
Need cached expensive calculation? Use useMemo
-
Need to reset state on prop change? Use key prop
-
Need to synchronize with external system? Use Effect with cleanup
-
Need non-reactive code in Effect? Use useEffectEvent
-
Need mutable value that doesn't trigger render? Use ref
When to Use Effects
Synchronizing with external systems: browser APIs (WebSocket, IntersectionObserver), third-party non-React libraries, window/document event listeners, non-React DOM elements (video, maps).
When NOT to Use Effects
-
Derived state — calculate during render
-
Expensive calculations — use useMemo
-
Resetting state on prop change — use key prop
-
Responding to user events — use event handlers
-
Notifying parent of state changes — update both in the same event handler
-
Chains of effects — calculate derived state and update in one event handler
Refs
-
Use for values that don't affect rendering (timer IDs, DOM node references)
-
Never read or write ref.current during render; only in event handlers and effects
-
Use ref callbacks (not useRef in loops) for dynamic lists
-
Use useImperativeHandle to limit what parent can access
Custom Hooks
-
Share logic, not state — each call gets an independent state instance
-
Name useXxx only if it actually calls other hooks; otherwise use a regular function
-
Avoid lifecycle hooks (useMount , useEffectOnce ) — use useEffect directly so the linter catches missing deps
-
Keep focused on a single concrete use case
Component Patterns
-
Controlled: parent owns state; uncontrolled: component owns state
-
Prefer composition with children over prop drilling; use Context only for truly global state
-
Use flushSync when you need to read the DOM synchronously after a state update
See react-patterns.md for code examples and detailed patterns.