WritingReact Performance FAQ: Answers to the Questions Your Team Keeps Asking — Clixo
6 min readreact, performance, faq, optimization, memoization, profiling

React Performance FAQ: Answers to the Questions Your Team Keeps Asking

Straightforward answers to the most common React performance questions — re-renders, memoization, profiling, bundle size, virtualization, and when each tool applies.

React performance questions come up repeatedly in engineering teams — in code reviews, in Slack threads, during architecture discussions. The same questions surface because the answers are not always obvious and the ecosystem has produced contradictory advice over the years. This FAQ consolidates the most common questions with direct, practical answers.

Why does my React component re-render so often?

React re-renders a component when its state changes, its props change, its context changes, or its parent re-renders. That last case is the most common cause of unexpectedly frequent re-renders: a parent holding frequently-changing state re-renders all of its children even if the children do not use that state.

The fix is almost always structural. Move the frequently-changing state to the component that actually uses it, or split the component tree so children that do not need the state are not descendants of the component holding it.

Are re-renders always a performance problem?

No. React is designed around re-rendering. A component that re-renders in under 1-2ms is not a performance problem, even if it re-renders frequently. The total time spent in rendering is what matters, not the count.

Re-renders become a problem when they are expensive (the component does significant work on each render) or when they cascade across a large portion of the component tree, adding up to a noticeable frame drop. Use the React DevTools Profiler to measure actual render times before deciding a re-render frequency is problematic.

Does React.memo always prevent re-renders?

No. React.memo prevents a component from re-rendering when its props are unchanged by reference. If any prop is an object, array, or function that is recreated on each parent render, the prop reference is new every time and React.memo does nothing.

For React.memo to work, every non-primitive prop must be stable: objects and arrays memoized with useMemo, functions stabilized with useCallback. One unstable prop anywhere breaks the optimization entirely.

What is the difference between useMemo and useCallback?

useMemo memoizes the return value of a function. useCallback memoizes the function itself. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

Use useMemo when you want to cache the result of an expensive computation. Use useCallback when you need a stable function reference — either to pass to a React.memo-wrapped child, or to use in a hook's dependency array without triggering unintended re-runs.

Should I wrap all my functions in useCallback?

No. useCallback is only useful in two specific situations: when the function is passed as a prop to a React.memo-wrapped component, or when the function is listed as a dependency in a useEffect or another hook and you need it to be stable.

For functions that are only called inside the component where they are defined — event handlers, utility functions, computed values — useCallback adds overhead without benefit. The function is recreated on each render, but recreating a small function is cheap; the dependency comparison in useCallback is not necessarily cheaper.

How do I know if code splitting will help my application?

Run a bundle analysis using rollup-plugin-visualizer (Vite) or webpack-bundle-analyzer (Webpack). Look at the initial bundle size and which routes or features contribute most to it.

If your initial bundle is large and includes code for routes the user has not visited yet — admin panels, settings pages, reporting views — code splitting at the route level will reduce initial load time. The gain is proportional to how much code can be deferred.

If the initial bundle is already small (under 100-150KB gzipped), code splitting will have minimal impact and may not be worth the added complexity.

Why does adding React.memo seem to have no effect?

The most common reason: an unstable prop reference. An object, array, or function passed as a prop is recreated on every parent render, so React.memo's prop comparison always returns false (props changed) and the component re-renders anyway.

Check every prop the memoized component receives. Any non-primitive must be stabilized. Use the React DevTools Profiler with "Record why each component rendered" enabled — hovering over the component in the flame graph will show which prop changed, so you can target the unstable reference directly.

When should I use virtualization instead of memoization?

Use virtualization when the problem is the number of components in the DOM, not the cost of rendering each one. Memoization makes individual renders cheaper. Virtualization reduces how many renders happen at all by only mounting components visible in the viewport.

For lists over roughly 200 items, virtualization with react-window or react-virtual will outperform any memoization strategy. The performance gain is structural — fewer DOM nodes, less layout work for the browser — not just a React-layer optimization.

Is the React compiler a replacement for manual memoization?

Partially. The React compiler (in stable release as of 2025) automatically infers where memoization is safe and inserts it. For components in straightforward patterns, this eliminates the need for manual useMemo and useCallback.

The compiler is not a complete replacement in all cases: it cannot handle memoization that depends on runtime conditions it cannot statically analyze, and it does not virtualize lists or split bundles. Manual memoization and structural optimizations remain relevant, but the set of cases requiring manual intervention is smaller with the compiler enabled.

How do I measure if a performance optimization actually worked?

Before making any change, record the specific interaction using the React DevTools Profiler and note the commit times. Make exactly one change. Record again. Compare the commit times.

For load performance improvements (bundle size, code splitting), use Lighthouse or WebPageTest with a throttled connection. Record the Time to Interactive before and after. Do not compare desktop Chrome performance — measure on a simulated mobile device where the gains are more visible and more representative of real users.

What should I fix first in a slow React app?

In this order:

  1. Profile to identify which components are slow and why.
  2. Fix state location — move state down to where it is used.
  3. Fix context over-rendering — split contexts or memoize context values.
  4. Virtualize long lists.
  5. Add React.memo to slow components with stable props.
  6. Reduce bundle size with code splitting and dependency auditing.

The order matters. Structural fixes (1-3) eliminate the root cause. Memoization (5) is a targeted tool for specific problems that structure could not solve. Bundle optimization (6) is a separate concern from render performance entirely.

If your team has a React application where performance issues have surfaced and you want a hands-on engineering partner, Clixo builds and optimizes production React applications.