Skip to content

Latest commit

 

History

History
99 lines (75 loc) · 2.82 KB

File metadata and controls

99 lines (75 loc) · 2.82 KB
paths
**/*.ts
**/*.tsx

Performance Rules

Colocate state with consumers

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 />
  </>
)

Prefer derived/computed values over redundant state

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)

Config-driven patterns over runtime branching

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'
}

Fail fast with assertions

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 bug

Avoid unnecessary re-renders from object/array literals

Don'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} />

Lazy initialization for expensive defaults

// Bad — runs on every render
const [state, setState] = useState(computeExpensiveDefault())

// Good — runs only on first render
const [state, setState] = useState(() => computeExpensiveDefault())