How to Find Wasted Renders with the React Profiler

This guide is part of React Rendering Performance, within Framework Performance. When a React interaction feels slow, the question is always: which components rendered, why, and how long did each take? The React DevTools Profiler answers all three. It records every commit during a session, showing each component's render time, whether it rendered at all, and — with the right setting enabled — the reason it rendered: changed props, changed state, changed context, or a parent re-render.

Wasted renders — components that re-render without any change in output — are the most common React performance problem, and the Profiler makes them visible in minutes. Combined with the browser's Performance panel for total interaction cost, it turns vague slowness into a short list of components to fix.

Profiler workflow Steps for using the React Profiler to find and fix wasted renders. Profiler workflow Enable reasons why each rendered Record one interaction Ranked view most expensive Read reasons props, context, parent Fix + re-record compare commits

Rapid Diagnosis

  • Install React DevTools and open the Profiler tab.
  • Enable "Record why each component rendered while profiling" in the Profiler settings (gear icon).
  • Enable "Highlight updates when components render" in Components settings for a quick visual check.
  • Use a production profiling build (react-dom/profiling) for realistic timings on deployed sites.

Root Cause Analysis: Common Wasted Render Sources

1. Parent re-renders. Children re-render because the parent did, with identical props.

2. Unstable props. New objects, arrays or callbacks created each render defeat memoization.

3. Broad context. A context value changes, re-rendering every consumer.

4. State too high. Local state in a high component re-renders its whole subtree.

Step-by-Step Resolution

1. Record one interaction at a time

Start recording, perform a single interaction (type one character, click one filter), stop. Short recordings make commits easy to read.

2. Read the flame and ranked charts

The flame chart shows the tree for a commit: wide, yellow components took longest; grey components did not render. The ranked chart sorts components by render time. Start with the top entries.

3. Check why each component rendered

Select a component: the right panel lists "Why did this render?" — for example "Props changed: (onSelect)", "Context changed", or "The parent component rendered". Unstable callbacks and objects appear as changed props even when their content is the same.

javascript
// Profiler says: "Props changed: (filters)". The filters object is recreated each render.
function Page() {
  const [sort, setSort] = useState('price');
  const filters = useMemo(() => ({ inStock: true, category: 'shoes' }), []);  // stable identity
  return <ProductList filters={filters} sort={sort} />;
}
// trade-off: useMemo only helps if ProductList is memoized (React.memo) or uses
// filters in an effect dependency; otherwise it adds overhead for nothing.

4. Fix and compare

Apply a fix (colocate state, memoize a component, stabilise a prop, split context), record the same interaction again, and compare commit duration and the number of rendered components.

Profiler render reasons and typical fixes Reasons shown by the React Profiler for a component render and the usual fix for each. Profiler render reasons and typical fixes Reason shown Likely cause Typical fix The parent component rendered unmemoized child React.memo or move state down Props changed (callback) new function each render useCallback or compiler Props changed (object) new object each render useMemo or hoist constant Context changed broad context value split context or selector Hook changed state update in this component expected; check frequency

Verification

The same interaction should show fewer components rendered (more grey in the flame chart) and a shorter commit duration. Verify the total interaction time in the browser Performance panel with CPU throttling, since React's commit is only part of the work. In the field, check INP for the interaction target.

Worked Example: A Booking Calendar

A booking app's calendar re-rendered slowly when a user hovered over time slots. The Profiler showed each hover committed 1,400 components in 90ms. "Why did this render?" showed that every TimeSlot re-rendered because "The parent component rendered" — the calendar stored hoveredSlot in its own state. The team moved hover styling to CSS (:hover) for visual feedback and kept only the selected slot in state, passed to TimeSlot as a boolean isSelected prop on a memoized component. Hovering no longer committed at all; selecting a slot committed 2 components in 1ms. INP for slot selection fell from 210ms to 60ms.

Using the Profiler on Production Builds

Development builds include extra checks that slow rendering and Strict Mode double-invokes renders, so timings are inflated. For realistic numbers, build with the profiling-enabled React DOM (aliasing react-dom$ to react-dom/profiling in the bundler), which keeps production optimizations while enabling the Profiler. Many teams keep a profiling build available in staging for this purpose. The <Profiler> component can also log render timings programmatically for specific subtrees, which is useful for automated performance tests.

javascript
import { Profiler } from 'react';
function onRender(id, phase, actualDuration) {
  if (actualDuration > 16) console.warn(`[${id}] ${phase} took ${actualDuration.toFixed(1)}ms`);
}
<Profiler id="ProductGrid" onRender={onRender}><ProductGrid /></Profiler>
// trade-off: Profiler timings are only collected in development and profiling
// builds; in normal production builds onRender is not called.

Components rendered per hover in a booking calendar Bar chart of how many components rendered per hover interaction before and after fixing a wasted render source. Components rendered per hover in a booking calendar Hover state in calendar 1400 CSS hover + memoized slots 0

Reading the Timeline for Long Sessions

For interactions that involve several commits — a drag, a multi-step form, typing a word — the Profiler's commit selector at the top shows a bar per commit, with height proportional to duration. Tall bars indicate the expensive commits to inspect first. Clicking through commits while watching which components render reveals patterns such as a component that renders on every commit even though its output only changes once. Use the filter setting to hide commits below a threshold (for example 2ms) so only meaningful ones remain.

Common Mistakes

  • Profiling long sessions. Too many commits to interpret.
  • Trusting development timings. Use profiling builds for absolute numbers.
  • Fixing the fastest components. Start with the ranked chart's top entries.
  • Ignoring browser work. Commit time excludes style, layout and paint.

Edge Cases

Server Components. Only client components appear in the Profiler; server work is not shown.

Concurrent renders. Interrupted renders may appear differently; focus on committed work.

Third-party components. Libraries may re-render internally; check their memoization options.

Effects. Expensive useEffect work runs after commit and does not appear as render time; use the Performance panel to see it.

FAQ

How do I see why a component re-rendered?

Enable "Record why each component rendered while profiling" in Profiler settings, record, then select a component to see the reason.

What do grey components in the flame chart mean?

They did not render in that commit, which is what you want for unaffected parts of the tree.

Can I profile a production site?

Only with a profiling build of React. Standard production builds do not expose Profiler data.

What is a commit?

The phase where React applies changes to the DOM after rendering. Each state update batch results in one commit.

Is a wasted render always a problem?

No. Cheap wasted renders are harmless. They matter when they are frequent, numerous or expensive.

Does highlighting updates help?

It gives a quick visual sense of what re-renders on each interaction, useful for spotting broad re-renders before profiling in detail.

How does the Profiler relate to INP?

Render and commit time are part of an interaction's processing and presentation time. INP also includes the browser's style, layout and paint, which the Performance panel shows.

Can I automate render checks in tests?

Yes. The <Profiler> component's onRender callback, used in a profiling build, can assert that key interactions stay under a render-time budget.

Why do some components render twice in development?

Strict Mode intentionally double-invokes renders in development to surface side effects. It does not happen in production builds.

Does the Profiler slow the app down?

Slightly, while recording. Compare runs under the same conditions rather than reading absolute timings too literally.

/html>