Skill: Profile React Performance

May 25, 2026 · View on GitHub

Identify unnecessary re-renders and performance bottlenecks in React Native apps using React Native DevTools.

Quick Command

# Open React Native DevTools (press 'j' in Metro terminal)
# Or shake device → "Open DevTools"
# Go to Profiler tab → Start profiling → Perform actions → Stop

For targeted audits, profile the exact flow under review. Baseline output should include commit timeline, re-render counts, slow components, and a breakdown of the heaviest commit.

When to Use

  • App feels sluggish or janky during interactions
  • Need to identify which components re-render unnecessarily
  • Investigating slow list scrolling or form inputs
  • Before applying memoization or state management changes

Prerequisites

  • React Native DevTools accessible (press j in Metro or use Dev Menu)
  • App running in development mode
  • React DevTools version 6.0.1+ for React Compiler support

Note: This skill involves visual profiler output (flame graphs, component highlighting). Use agent-device for runnable scenario evidence; install it through the environment's approved/trusted path or ask the user if verification needs it and it is missing. Profiler analysis may still require the DevTools UI, exported data, or human review.

Step-by-Step Instructions

1. Open React Native DevTools

# Option A: Press 'j' in Metro terminal (works with both RN CLI and Expo)
# Option B: Shake device / Cmd+D (iOS) / Cmd+M (Android) → "Open DevTools"
# Expo: Also accessible via Expo DevTools in browser

2. Configure Profiler Settings

  1. Go to Profiler tab
  2. Click gear icon (⚙️) for settings
  3. Enable:
    • "Highlight updates when components render"
    • "Record why each component rendered while profiling"

3. Record a Profiling Session

1. Click "Start profiling" (blue circle) or "Reload and start profiling"
2. Perform the exact interaction or navigation flow you want to analyze
3. Click "Stop profiling"

Use "Reload and start profiling" for startup performance analysis.

For AI-agent workflows, treat this as a required sequence:

  1. Start profiling.
  2. Drive the audited flow, not just app startup or idle state.
  3. Stop profiling.
  4. Inspect commit timeline, re-renders, slow components, and the heaviest commit before proposing fixes.

4. Analyze the Flame Graph

React DevTools Flamegraph

The flame graph shows component render hierarchy with timing:

Color indicators:

  • Yellow components: Most time spent rendering (focus here)
  • Green components: Fast/memoized
  • Gray components: Did not render

Right panel shows "Why did this render?":

  • Props changed (shows which prop, e.g., children, onPress)
  • Rendered at timestamps with duration (e.g., "3.7s for 0.9ms")

Click on a component to see:

  • Why it rendered (hook change, props change, parent re-render)
  • Render duration
  • Child components affected

5. Use Ranked View for Bottom-Up Analysis

Click "Ranked" tab to see components sorted by render time (slowest first).

6. Profile JavaScript CPU

For non-React performance issues:

  1. Go to JavaScript Profiler tab (enable in settings if hidden)
  2. Click "Start" to record
  3. Perform actions
  4. Click "Stop"
  5. Use Heavy (Bottom Up) view to find slowest functions

Code Examples

Before: Unnecessary Re-renders

const App = () => {
  const [count, setCount] = useState(0);
  
  return (
    <View>
      <Text>{count}</Text>
      {/* Button re-renders on every count change */}
      <Button onPress={() => setCount(count + 1)} title="Press" />
    </View>
  );
};

const Button = ({onPress, title}) => (
  <Pressable onPress={onPress}>
    <Text>{title}</Text>
  </Pressable>
);

After: Memoized

const App = () => {
  const [count, setCount] = useState(0);
  const onPressHandler = useCallback(() => setCount(c => c + 1), []);
  
  return (
    <View>
      <Text>{count}</Text>
      <Button onPress={onPressHandler} title="Press" />
    </View>
  );
};

const Button = memo(({onPress, title}) => (
  <Pressable onPress={onPress}>
    <Text>{title}</Text>
  </Pressable>
));

Interpreting Results

SymptomLikely CauseSolution
Many yellow componentsCascading re-rendersAdd memoization or use React Compiler
"Props changed" on callbacksInline functions recreatedUse useCallback
"Parent component rendered"State too high in treeMove state down or use atomic state
Long JS thread blockHeavy computationMove to background or use useDeferredValue

Only propose callback or dependency-array changes when the profiler or a reproducible bug shows they matter. Do not infer stale closures from a snippet alone.

Common Pitfalls

  • Profiling in dev mode: Always disable JS Dev Mode for accurate measurements (Settings > JS Dev Mode on Android)
  • Not using production builds: Some issues only appear with minified code
  • Ignoring "Why did this render?": This tells you exactly what to fix
  • Using component tree depth or count as the main baseline: These are secondary context, not the core performance signal