All posts
React NativeMobilePerformanceJavaScript

React Native Performance: 8 Tips That Actually Matter

Performance problems in React Native apps are rarely where you expect them. Here are the 8 optimisations I apply to every production app.

28 February 20263 min read

React Native apps can feel buttery smooth or frustratingly janky — and the difference often comes down to a handful of well-known pitfalls. After shipping a dozen production apps, here are the eight optimisations I apply every time.

1. Use the New Architecture (Fabric + JSI)

If you're on React Native 0.71+, enable the New Architecture. The old bridge was the root cause of most performance ceilings.

// android/gradle.properties
newArchEnabled=true
# ios/Podfile
ENV['RCT_NEW_ARCH_ENABLED'] = '1'

The New Architecture eliminates the async bridge entirely, enabling synchronous calls between JS and native.

2. Stop Re-rendering Everything

The most common cause of jank is unnecessary re-renders. Use React.memo, useMemo, and useCallback strategically:

// Bad — new function reference on every render
<FlatList
  data={items}
  renderItem={({ item }) => <ItemCard item={item} />}
/>

// Good — stable reference
const renderItem = useCallback(
  ({ item }: { item: Item }) => <ItemCard item={item} />,
  [] // no deps — ItemCard handles its own data
);

<FlatList data={items} renderItem={renderItem} />

3. FlatList Configuration

FlatList is your most performance-critical component. These props matter:

<FlatList
  data={items}
  renderItem={renderItem}
  keyExtractor={(item) => item.id}
  // Reduces initial render cost
  initialNumToRender={10}
  // How far ahead to render
  windowSize={5}
  // Remove off-screen components from memory
  removeClippedSubviews={true}
  // Batch state updates
  updateCellsBatchingPeriod={50}
  maxToRenderPerBatch={10}
/>

4. Avoid Inline Styles

Inline style objects are recreated on every render. Use StyleSheet.create:

// Bad
<View style={{ flex: 1, backgroundColor: "#fff", padding: 16 }}>

// Good
const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#fff", padding: 16 },
});
<View style={styles.container}>

5. Use Hermes

Hermes is a JS engine optimised for React Native. Enable it:

// android/app/build.gradle
project.ext.react = [
  enableHermes: true,
]

Hermes pre-compiles JS to bytecode at build time, significantly reducing startup time (often 20–40% faster TTI).

6. Image Optimisation

Images are a major culprit for memory issues. Always:

// Use explicit dimensions — avoids layout thrashing
<Image
  source={{ uri: imageUrl }}
  style={{ width: 200, height: 200 }}
  // Cache on disk
  resizeMode="cover"
/>

For production apps, use expo-image or react-native-fast-image — they handle caching, progressive loading, and memory management far better than the built-in Image.

7. Offload Heavy Work to the UI Thread

Use InteractionManager to defer expensive operations until after animations complete:

useEffect(() => {
  const task = InteractionManager.runAfterInteractions(() => {
    // Heavy computation, large data processing, etc.
    loadLargeDataset();
  });

  return () => task.cancel();
}, []);

8. Profile Before Optimising

Don't guess — use the built-in tools:

  • Flipper with the React DevTools plugin
  • Performance Monitor (shake device → Show Perf Monitor)
  • Systrace for native thread profiling

The JS thread and UI thread FPS tell you exactly where your bottleneck is. Most of the time it's one of the issues above.

Summary

| Issue | Fix | |---|---| | Bridge overhead | Enable New Architecture | | Unnecessary re-renders | memo, useCallback, useMemo | | FlatList jank | windowSize, removeClippedSubviews | | Style recreation | StyleSheet.create | | Slow startup | Enable Hermes | | Image memory | react-native-fast-image | | Animation jank | InteractionManager | | Unknown bottleneck | Profile first! |

Performance optimisation is a loop, not a checklist. Profile, fix the worst offender, repeat.