webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
React · Lesson 6 of 7

Performance in React

Why a component re-renders, memo, useMemo, useCallback, useTransition, virtualization.

Updated

What performance means in React

A React app is usually slow for one of two reasons: it re-renders too much (components that haven't changed are recomputed) or it does too much work in one render (a list of 5,000 rows, a heavy calculation). Before any optimization, measure — with React DevTools → Profiler.

When a component re-renders

  1. Its own state changed.
  2. Its parent re-rendered (by default, all children re-render — even if the props are identical).
  3. A Context it reads changed its value.

A re-render does not mean a DOM change — React compares the result and touches the DOM only where there's a difference. Re-renders are usually cheap; they become a problem with big or very frequent components.

The tools — what each one does

Tool What it does When
moving state down only the small component re-renders the first option, free
children as a prop the passed content doesn't re-render with the parent stateful wrappers (a modal, an accordion)
memo(Component) skips re-rendering if the props are equal (Object.is) an expensive component, often re-rendered with the same props
useMemo(() => calc, deps) keeps a calculation's result a heavy calculation or an object passed to a memo
useCallback(fn, deps) keeps the same function between renders a function passed to a memo or used in deps
useTransition / useDeferredValue marks an update as non-urgent filtering a big list while typing — the input stays smooth
virtualization renders only the visible rows lists of thousands of items (TanStack Virtual)

The React Compiler

The React Compiler analyzes the code at build time and automatically adds memoization (the equivalent of memo / useMemo / useCallback) where it's needed. With it enabled, you write simple code, and most manual memoization becomes unnecessary. Next.js supports it through the reactCompiler option in next.config.ts.

The condition: the code follows the rules of React — pure components, no mutating props or state.

The reference trap

const Row = memo(function Row({ item, onSelect }) { ... })

function List({ items }) {
  return items.map(item => (
    <Row key={item.id} item={item} onSelect={() => select(item.id)} />   // a NEW function on every render
  ))
}

memo compares props by reference; a function or object created inline is always "new" → memo doesn't help. Hence useCallback / useMemo — or the React Compiler.

The order in which to optimize

  1. Measure (Profiler): which component, how long, why it re-renders.
  2. Move state closer to where it's used; split components.
  3. Server Components for everything that isn't interactive — zero cost on the client.
  4. Virtualization for long lists; useTransition for heavy updates.
  5. Targeted memo / useMemo / useCallback — or turn on the React Compiler.

Summary

  • A re-render = its own state, a re-rendered parent or a changed Context; measure with the Profiler.
  • Structure first (state lower, children), then memo + useMemo / useCallback.
  • useTransition for heavy updates, virtualization for big lists, the React Compiler for automatic memoization.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.