OB
All posts
React Native Performance Mobile

React Native Performance: What Nobody Tells You

February 15, 20243 min readby MHD Omar Bahra

The Real Bottleneck

Most React Native performance advice starts with "use FlatList instead of ScrollView" and ends there. That's fine for beginners, but production apps with complex UIs hit problems that go much deeper.

The root cause of most React Native jank is the JavaScript thread doing too much work during renders. Every setState, every re-render, every anonymous function passed as a prop — it all runs on one thread. And if that thread is busy, your UI freezes.

Understand the Architecture First

React Native has two main threads:

  • JS thread — runs your JavaScript logic and React reconciliation
  • UI thread (main thread) — renders native views and handles gestures

They communicate over a bridge (or the new JSI in the new architecture). That communication has a cost. Sending large payloads or calling it too frequently is where performance dies.

// ❌ This serializes a large object across the bridge on every render
<FlatList
  data={items}
  renderItem={({ item }) => <ExpensiveComponent data={item} onPress={() => handlePress(item)} />}
/>

// ✅ Extract and memoize the render function
const renderItem = useCallback(({ item }) => (
  <ExpensiveComponent data={item} onPress={handlePress} />
), [handlePress])

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

FlatList: The Wrong Default Settings

FlatList's defaults are not tuned for performance. These props matter:

<FlatList
  data={items}
  renderItem={renderItem}
  keyExtractor={item => item.id}
  // Render 10 items outside the viewport (instead of default 21)
  windowSize={5}
  // Remove items from memory when far off-screen
  removeClippedSubviews={true}
  // How many items to render in the initial batch
  initialNumToRender={10}
  // Max items rendered per JS frame
  maxToRenderPerBatch={5}
  // Delay between batch renders (ms)
  updateCellsBatchingPeriod={30}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

getItemLayout is the biggest win if your items have a fixed height. It lets FlatList skip measuring and jump directly to any scroll position.

Memoization Is Not Free

React.memo, useMemo, and useCallback have overhead. They run a comparison on every render. For simple values the comparison costs more than just re-rendering.

Use them when:

  • The component is genuinely expensive to render
  • The prop is a function or object that would fail reference equality
// ❌ Pointless — comparing a string is as cheap as the memo overhead
const Title = React.memo(({ text }) => <Text>{text}</Text>)

// ✅ Worth it — prevents re-rendering a complex list item
const ProductCard = React.memo(({ product, onAddToCart }) => {
  // ... complex render with images, animations
}, (prev, next) => prev.product.id === next.product.id)

Move Work Off the JS Thread

For animations, use react-native-reanimated. It runs animations on the UI thread directly, bypassing the bridge entirely.

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated'

const offset = useSharedValue(0)

const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ translateX: offset.value }],
}))

// This runs on the UI thread — zero JS thread involvement
offset.value = withSpring(100)

For heavy computations (parsing, sorting, transforming large datasets), offload to a worker with react-native-workers or batch the work using InteractionManager:

InteractionManager.runAfterInteractions(() => {
  // Runs after animations and interactions settle
  const processed = heavyDataTransformation(rawData)
  setData(processed)
})

The Profiler Is Your Best Friend

Before optimizing anything, profile first. In Flipper, the React DevTools Profiler shows exactly which components re-render and why. In 90% of cases, the problem is one component re-rendering with stale props — not a global architectural issue.

Fix the specific thing the profiler shows. Don't blindly wrap everything in memo.

Enjoyed this post?

Subscribe to the newsletter

Get future posts delivered to your inbox. No spam, unsubscribe anytime.