Skip to main content

React DevTools Profiler Guide

Use the React DevTools Profiler to find and fix unnecessary re-renders, understand flame graphs, and apply React.memo / useMemo / useCallback where they actually matter.

✓ Flame graph reading ✓ Re-render diagnosis ✓ Memoization guide ✓ Anti-pattern fixes

Profiler Workflow

  1. Install React DevTools extension (Chrome Web Store or Firefox Add-ons)
  2. Open DevTools → React tab → Profiler sub-tab
  3. Enable "Record why each component rendered" in Profiler settings (gear icon)
  4. Click Record, perform the slow interaction, click Stop
  5. Examine the flame graph — look for wide yellow bars (slow components)
  6. Click a bar to see why it rendered (prop/state change details)

Reading the Flame Graph

Bar Color Meaning Action
YellowSlowest relative to other rendersInvestigate — memoize or optimize
GreenFast renderNo action needed
GreyDid not render (skipped)Desired — memoization worked
BlueMounted for first timeExpected on initial render

React.memo — Prevent Parent-Triggered Re-renders

// WITHOUT memo: re-renders every time Parent re-renders
function ExpensiveChart({ data, title }: Props) {
  return <svg>{/* complex chart rendering */}</svg>;
}

// WITH memo: only re-renders when data or title change (shallow compare)
const ExpensiveChart = React.memo(function ExpensiveChart({ data, title }: Props) {
  return <svg>{/* complex chart rendering */}</svg>;
});

// Custom comparator for deep comparison or selective prop ignoring
const ExpensiveChart = React.memo(
  function ExpensiveChart({ data, title, onHover }: Props) {
    return <svg>{/* ... */}</svg>;
  },
  (prev, next) => prev.data === next.data && prev.title === next.title
  // ↑ ignores onHover changes — only re-render when data or title changes
);

useCallback — Stable Function References

// PROBLEM: new arrow function on every render breaks React.memo
function Parent() {
  const [count, setCount] = useState(0);

  // BAD: new reference every render → MemoizedChild always re-renders
  return <MemoizedChild onClick={() => setCount(c => c + 1)} />;
}

// SOLUTION: stable reference with useCallback
function Parent() {
  const [count, setCount] = useState(0);

  // GOOD: same function reference unless deps change
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []); // empty deps = never recreated

  return <MemoizedChild onClick={handleClick} />;
}

useMemo — Expensive Calculations

// BAD: recalculates on every render
function UserList({ users, filter }: Props) {
  // This runs even when unrelated state changes
  const filtered = users.filter(u => u.role === filter);
  return filtered.map(u => <UserRow key={u.id} user={u} />);
}

// GOOD: only recalculates when users or filter changes
function UserList({ users, filter }: Props) {
  const filtered = useMemo(
    () => users.filter(u => u.role === filter),
    [users, filter]   // recalculate only when these change
  );
  return filtered.map(u => <UserRow key={u.id} user={u} />);
}

// Rule of thumb: use useMemo only when:
// 1. The calculation takes > 1ms (profile first!)
// 2. The result is passed to a React.memo component as a prop
// 3. The result is used as a dependency of useEffect/useCallback

Programmatic Profiling with the Profiler Component

import { Profiler, ProfilerOnRenderCallback } from 'react';

const onRender: ProfilerOnRenderCallback = (
  id,           // component name
  phase,        // 'mount' or 'update'
  actualDuration, // time for this render
  baseDuration,   // estimated time without memoization
  startTime,
  commitTime
) => {
  if (actualDuration > 16) {  // flag renders that miss 60fps
    console.warn(`Slow render in ${id}: ${actualDuration.toFixed(2)}ms`);
    // Send to monitoring: analytics.track('slow_render', { id, actualDuration });
  }
};

// Wrap your slow subtree
<Profiler id="DashboardChart" onRender={onRender}>
  <DashboardChart data={chartData} />
</Profiler>

Common Anti-Patterns and Fixes

Frequently Asked Questions

How do I open the React DevTools Profiler?▼

Install the React DevTools browser extension (Chrome or Firefox). Open DevTools (F12 or Cmd+Opt+I) and look for the React tab, which has two sub-tabs: Components (inspect the component tree) and Profiler (record and analyze renders). The Profiler tab shows a Record button — click it, perform the interaction you want to profile, then stop recording. React DevTools only works in development mode (process.env.NODE_ENV === 'development').

What does a Profiler flame graph show?▼

The flame graph shows the component render tree for a single commit (React re-render cycle). Each bar represents a component — width indicates render duration, color indicates time relative to other renders (yellow = slow, green = fast, grey = did not render). Click any bar to see why that component rendered: it shows which props or state changed. Bars that are grey (did not render) mean React skipped them — this is the desired outcome after memoization.

When should I use React.memo, useMemo, and useCallback?▼

React.memo wraps a component to prevent re-render when parent re-renders and props are unchanged — use it for expensive components that receive stable props. useCallback memoizes a function reference — use it when passing callbacks to React.memo'd child components to prevent prop equality failures. useMemo memoizes a computed value — use it for expensive calculations that depend on specific inputs. Do NOT apply these reflexively; measure with the Profiler first, then add memoization only where renders are measurably slow.

Why does React re-render a memoized component even though I added React.memo?▼

React.memo does a shallow comparison of props. Re-renders still happen if: (1) a prop is an object or array literal created inline — a new reference is created on every parent render; (2) a callback prop is an arrow function without useCallback — same issue; (3) a context value changes and the component is a consumer; (4) the component calls useState or useReducer and the local state changes. The fix is to ensure all object and function props are stable references via useMemo/useCallback, or refactor to avoid passing unstable props.

What is the useProfiler hook and when should I use it?▼

React's Profiler component (not the DevTools) is a built-in React component that lets you measure render performance programmatically. Wrap any subtree with <Profiler id='name' onRender={callback}> and the onRender callback is called with render duration, phase (mount vs update), and component path. Use it to collect performance data in staging builds that you send to monitoring services. Unlike DevTools, it works in production builds but should be conditional to avoid overhead in hot paths.

What are React's most common performance anti-patterns?▼

The most impactful anti-patterns: (1) Placing expensive components inside render functions of frequently-updating parents — extract them or lift state down; (2) Putting large state objects in context — context re-renders all consumers on any change; (3) Using array index as key in lists with dynamic order — keys should be stable IDs; (4) Not virtualizing long lists — use react-window or react-virtual for lists over 100 items; (5) Missing dependency arrays in useEffect/useMemo/useCallback — causes re-runs on every render.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →