| paths | ||
|---|---|---|
|
Minimizes re-render blast radius. The component that uses the data should own the hook call.
// Bad — parent re-renders both children
const Parent = () => {
const price = usePrice()
return (
<>
<PriceDisplay price={price} />
<OrderForm /> {/* re-renders for no reason */}
</>
)
}
// Good — only PriceDisplay re-renders when price changes
const Parent = () => (
<>
<PriceDisplay /> {/* owns its price hook internally */}
<OrderForm />
</>
)Never store what can be computed. Redundant state creates sync bugs and extra re-renders.
// Bad — extra state + useEffect to keep in sync
const [items, setItems] = useState<Item[]>([])
const [total, setTotal] = useState(0)
useEffect(() => setTotal(items.reduce((s, i) => s + i.price, 0)), [items])
// Good — computed on each render (React Compiler optimizes this)
const [items, setItems] = useState<Item[]>([])
const total = items.reduce((sum, item) => sum + item.price, 0)Records and config arrays are evaluated once. if/else chains and switch/case are evaluated on every render.
// Good — O(1) lookup, evaluated once
const colorMap: Record<Status, string> = { active: 'green', inactive: 'gray' }
const color = colorMap[status]
// Bad — O(n) branching on every render
const getColor = (status: Status) => {
if (status === 'active') return 'green'
if (status === 'inactive') return 'gray'
}Invalid state should crash immediately at the boundary, not propagate silently through the component tree causing hard-to-debug rendering issues.
// Good — fails immediately with clear error
const token = ensurePresent(tokens.find(t => t.id === selectedId), 'selected token')
// Bad — silently propagates undefined, crashes later in a child component
const token = tokens.find(t => t.id === selectedId)
return <TokenCard name={token?.name ?? ''} /> // silent fallback hides the bugDon't create new object or array references in render if they're used as props or deps.
// Bad — new array on every render, causes child re-render
<Component items={items.filter(i => i.active)} />
// Good — derive outside JSX (React Compiler handles memoization)
const activeItems = items.filter(i => i.active)
return <Component items={activeItems} />// Bad — runs on every render
const [state, setState] = useState(computeExpensiveDefault())
// Good — runs only on first render
const [state, setState] = useState(() => computeExpensiveDefault())