React Performance Optimization
Reduce wasted renders with memoization and stable references
Overview
Most React performance issues come from unnecessary re-renders and recreating work on every render. The core tools are React.memo to skip re-rendering unchanged components, useMemo to cache values, and useCallback to keep function identities stable. Measure first with the Profiler—optimize only proven hot paths.
Syntax / Usage
Wrap a component in React.memo so it only re-renders when its props change by reference.
import { memo, useCallback, useState } from 'react'
type RowProps = { label: string; onSelect: (label: string) => void }
const Row = memo(function Row({ label, onSelect }: RowProps) {
console.log('render', label)
return <li onClick={() => onSelect(label)}>{label}</li>
})
function List({ items }: { items: string[] }) {
const [selected, setSelected] = useState<string | null>(null)
const handleSelect = useCallback((label: string) => {
setSelected(label)
}, [])
return (
<ul>
{items.map((item) => (
<Row key={item} label={item} onSelect={handleSelect} />
))}
</ul>
)
}
Examples
Memoize a derived value so a costly computation runs only when inputs change:
const total = useMemo(
() => rows.reduce((sum, r) => sum + r.amount, 0),
[rows]
)
Avoid inline objects as props to memoized children—they break reference equality:
const style = useMemo(() => ({ padding: 8, color }), [color])
return <Badge style={style} />
Common Mistakes
- Wrapping everything in
memo/useMemowithout measuring—the overhead can exceed the savings - Passing new inline functions or objects to memoized components, defeating the memo
- Using array index as a
key, causing incorrect reuse and re-renders on reorder - Ignoring the React DevTools Profiler and guessing at bottlenecks
- Storing large derived state instead of computing it during render
See Also
use-memo use-callback react-context-patterns