Back to articles
ReactPerformanceDebugging

Finding the Root Cause of React Performance Problems

A tool-by-tool walkthrough — React DevTools Profiler, the Performance panel, and why-did-you-render — for finding what's actually making a React app slow, instead of guessing at useMemo.

Yash Thakur
4 min read
Finding the Root Cause of React Performance Problems

The short answer: don't guess — profile. Record an interaction in the React DevTools Profiler to see which components re-render and how long they take, use the browser Performance panel to find long tasks on the main thread, and only then apply the targeted fix. Sprinkling useMemo without a profile is how you add complexity and move nothing.

The most common performance mistake I see isn't slow code — it's guessing at slow code. Someone decides the app "feels laggy," wraps a dozen components in React.memo, and ships. Sometimes it helps; usually it doesn't, and now the code is harder to read. Performance is a measurement discipline. Here are the tools I actually open, in order, and what each one tells you.

Start with the React DevTools Profiler

The Profiler is the single best tool for React-specific slowness. Open DevTools → Profiler, hit record, perform the slow interaction, stop. You get a flame graph of every component that rendered, how long each took, and why it rendered.

Two features do most of the work:

  • The ranked chart shows the most expensive commits first — your hotspots, named.
  • "Why did this render?" (enable it in Profiler settings) tells you whether a component rendered because of props, state, hooks, or just because its parent did. That last one — "the parent rendered" — is the clue for most wasted work.

What you're hunting for: a component that re-renders on an interaction it has nothing to do with (typing in a search box re-rendering a 500-row table that didn't change), or one component eating 40ms of a 50ms commit.

Confirm with "why did you render"

When the Profiler says a component re-rendered on props but you expected it not to, the @welldone-software/why-did-you-render library tells you exactly which prop changed identity — usually an inline object or function that's a fresh reference every render.

This is where useMemo/useCallback + React.memo genuinely earn their place — applied to a measured hot path, not sprayed everywhere. (If you're on the React Compiler, much of this is handled for you; profile to confirm.)

Drop to the browser Performance panel for the main thread

React DevTools shows React work; the browser's Performance panel shows everything — including the non-React reasons your UI janks. Record the same interaction and look for:

  • Long tasks (the red-cornered blocks over 50ms): these block input and tank your INP.
  • Scripting vs rendering vs painting split: if you're spending 300ms in "Recalculate Style / Layout," that's a layout-thrash problem, not a React one.
  • Forced reflows: reading offsetHeight in a loop that also writes styles is a classic.

The Performance panel is how you catch the slowness that has nothing to do with component re-renders — a synchronous JSON parse, an un-debounced scroll handler, a huge list rendering 5,000 DOM nodes.

The lists problem deserves its own tool

If the hotspot is a long list, no amount of memoization fixes rendering thousands of DOM nodes. The answer is virtualization — render only what's visible. Reach for a windowing library and the Profiler will show the commit time collapse.

My actual workflow

  1. Reproduce the slow interaction reliably (same discipline as any bug).
  2. Profiler record → find the component(s) doing unexpected or expensive work.
  3. "Why did this render?" → is it props identity, or a parent re-render I can stop?
  4. Performance panel → is there a long task or layout thrash that isn't about React at all?
  5. Apply the one targeted fix the measurement points to — stabilize a reference, memo a hot component, virtualize a list, debounce a handler.
  6. Re-profile to confirm the number actually moved. If it didn't, revert the change — unverified "optimizations" are just complexity.

The throughline is the same as debugging any issue: measure to find the root cause, change one thing, verify. The tools above turn "the app feels slow" into "this specific component re-renders 60 times a second because of an inline callback" — and a precise cause is a problem you can actually fix.

Written by Yash Thakur

Senior React Developer · 8+ years building for the web

More articles

Keep reading