How to Find Unnecessary Re-renders with React Profiler

Record an interaction in React DevTools, read the flamegraph and ranked view, and spot components that re-render even though their props did not change.

6 min read

The React Profiler records which components rendered and how long each one took. An unnecessary re-render is a component that renders again even though its props and state are unchanged. The Profiler is how you find it, measure it, and confirm the fix.

Record the interaction

Start with one specific interaction, such as typing in a filter field or opening a panel. Recording one thing at a time keeps the results readable.

Open the Profiler tab

Open React DevTools and select the Profiler tab.

Start recording

Click the record button, then reproduce the slow interaction in your app.

Stop recording

Click record again to stop. DevTools shows one bar per render commit.

Keep the recording short. A few seconds of one interaction is easier to read than a minute of unrelated clicking.

Each bar is a commit, the moment React applied changes to the screen. Click a bar to open the flamegraph for that commit, or switch to the Ranked view for a list sorted by render time.

Read the flamegraph and ranked view

The flamegraph shows every component that rendered in that commit, nested the same way the tree is. A wide bar means the component took a long time to render.

The Ranked view lists components by total render duration, so the slowest work rises to the top. It also shows the commit time for each component, which lets you compare one commit against another after you change the code.

Look for a component that appears in many commits while its inputs never change. A list that re-renders on every keystroke of an unrelated search field is the classic example.

Spot the unnecessary re-render

Turn on Highlight updates, then replay the interaction. React DevTools draws a border around every component that re-renders, so components that flash on every update become obvious.

Imagine a search input and a product list that both live under one App component. Typing changes App state, so App re-renders, and the product list re-renders too even though the products array is the same. That list is the unnecessary re-render.

A re-render is only unnecessary when the component's props and state are identical to the previous render. Click the component in the flamegraph to inspect the props it received. If the props did not change but the component rendered anyway, you found the target.

An unrelated component flashing alongside it usually means the re-render comes from a shared parent, which points back to state placement.

Since React 19.2, a development or profiling build also adds a Components track to the browser's own Performance panel in Chromium-based browsers. Recording there shows the same render timings next to network and JavaScript activity, and clicking a render entry can show which props changed, which is useful when a re-render needs to be understood alongside other page activity rather than in isolation.

Measure programmatically with the Profiler component

The Profiler tab is interactive, but the Profiler component records the same numbers from your code, which is useful for automated checks and for capturing render timings in tests.

App.jsxApp.jsx
import { Profiler } from "react";
 
function onRender(id, phase, actualDuration) {
  console.log(`${id} rendered in ${phase}: ${actualDuration.toFixed(2)}ms`);
}
 
export default function App({ results }) {
  return (
    <Profiler id="ResultList" onRender={onRender}>
      <ResultList results={results} />
    </Profiler>
  );
}

The onRender callback receives the profiler id, the phase, and actualDuration. Phase is mount, update, or nested-update, which separates first paint from later work.

actualDuration is how long this subtree really took, while baseDuration estimates the cost with no memoization, so a gap between them shows room to optimize.

Fix it and verify

Once you know which component re-renders needlessly, apply the smallest fix. Move state down, pass JSX as children, or wrap the component in memo only when its props are stable. Then record the same interaction again and compare the commit duration.

A drop in the largest bar confirms the fix, while a flat number means the component was not the real cause.

If the number did not drop, the fix did not help and you can remove it. For the full decision loop, see how to optimize React performance without guessing. When the fix is memoization, memo and useMemo and useCallback are the tools, but only after the Profiler has shown they are needed.

Rune AI

Rune AI

Key Insights

  • Record one interaction at a time in the Profiler tab.
  • Use the Ranked view to find the most expensive render.
  • Toggle Highlight updates to see every re-render live.
  • A re-render is unnecessary when props and state did not change.
  • Fix, then record again to confirm the commit time dropped.
RunePowered by Rune AI

Frequently Asked Questions

Does the Profiler work in production?

The interactive Profiler tab works in development. The Profiler component is disabled in production builds unless you enable a profiling build.

Is every re-render a problem?

No. A re-render is only a problem when it is frequent, the props did not change, and the component's render is noticeably expensive.

Conclusion

The Profiler turns a slow interaction into a list of measured renders. Record the interaction, find the component that renders with unchanged props, fix the re-render with the smallest change, then record again to confirm the improvement.