React Native Performance Debugger
Goals
- Identify slow renders, JS thread stalls, and heavy network or disk work.
- Measure and reproduce issues reliably.
- Apply targeted fixes and verify improvements.
Core Tooling
- Flipper with React DevTools and Performance plugins.
- Hermes engine with Chrome DevTools integration (RN ≥ 0.71 defaults to Hermes).
- Android Studio Profiler and Xcode Instruments for native CPU/Memory/Network.
- React Profiler to audit component render costs.
Setup Checklist
- Enable Hermes (if not already):
- iOS: set
hermes_enabled: trueinios/Podfileandpod install. - Android: ensure
enableHermes: trueinandroid/app/build.gradle.
- iOS: set
- Install Flipper and plugins:
- Download Flipper, enable React DevTools, Network, and Performance plugins.
- Install React DevTools (optional for standalone):
npm i -D react-devtoolsand runnpx react-devtools.
Profiling Workflow
1) Baseline metrics
- Record app start time, first meaningful interaction, and frame rate.
- Use
Performance Monitor(Dev Menu → Perf Monitor) to watch FPS/UI/JS.
2) JS thread stalls
- In Flipper, open Performance → JS Thread Activity.
- Look for long tasks (>50ms). Typical culprits: heavy JSON parsing, large loops, synchronous storage.
3) Render overwork
- Use React Profiler to record interactions. Identify components with many renders or long render times.
- Fixes:
- Memoize expensive calculations with
useMemo. - Stabilize callback identities with
useCallback. - Avoid inline anonymous functions in hot lists and renders.
- Use
React.memofor pure components.
- Memoize expensive calculations with
Stabilize heavy props
const ExpensiveList = React.memo(({ items }: { items: Item[] }) => {
// render items
});
export function Screen({ data }: { data: Item[] }) {
const processed = useMemo(() => heavyProcess(data), [data]);
const onPress = useCallback((id: string) => handlePress(id), []);
return <ExpensiveList items={processed} />;
}
4) Lists
- Prefer
FlatList/SectionListwithkeyExtractor,getItemLayout, andinitialNumToRendertuned. - Use
removeClippedSubviews={true}for large lists. - Avoid
scrollToIndexon huge lists withoutgetItemLayout.
5) Network and storage
- In Flipper Network tab, check slow endpoints and large payloads.
- Cache results and paginate; avoid blocking the JS thread with synchronous storage.
6) Native hotspots
- Android Studio Profiler: CPU/Memory → record during slow action.
- Xcode Instruments: Time Profiler and Allocations.
Detecting Re-renders
Use why-did-you-render during development:
if (__DEV__) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const wdyr = require('@welldone-software/why-did-you-render');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ReactInstance = require('react');
wdyr(ReactInstance, { trackAllPureComponents: true });
}
Performance Budget
- JS long task budget: < 50ms per frame.
- First screen interactive: < 2s on mid-range devices.
- List scroll FPS: 55–60fps with no stutters.
Common Pitfalls
- Unbounded state in global stores causing cascade re-renders.
- Deriving large computed structures on each render.
- Heavy images without caching/resizing; use FastImage or preprocessed assets.
- Excessive layout passes; prefer flexbox simplicity and avoid nested views.
Verification
- Re-run Profiler traces after fixes; compare render time deltas.
- Use A/B flags in code to validate improvement quickly.
- Automate perf smoke tests on CI with lightweight launch-time metrics.
References
- React Native Performance docs
- Flipper plugins and setup
- Android and iOS native profiling guides