Skip to content
/Optimizing FlatList: Reducing Re-renders by 40%
Nov 2023·5 min read

Optimizing FlatList: Reducing Re-renders by 40%

A practical guide to memoisation, getItemLayout, windowSize, and keyExtractor techniques that eliminated 40% of unnecessary re-renders in a 100-screen React Native app.

PerformanceReact NativeFlatListMemoisation
Text-to-Speech unsupported
Text Size

The Yoke app has an Instagram-like feed with video Reels and complex list-heavy screens across 100+ pages. Profiling with Flipper's React DevTools revealed FlatList components re-rendering 2–3× more than necessary, causing dropped frames, sluggish scroll, and a noticeable stutter when swiping between Reels. Here's the systematic fix.

Step 1: Identify re-render sources

Flipper's React DevTools highlighted three sources in order of severity: inline arrow functions in renderItem (creates a new function reference every render), plain object props like contentContainerStyle written inline (new object reference each render), and parent state changes — like a typing indicator — propagating down to unchanged list items.

Fix 1: Memoize renderItem and keyExtractor

Before vs After
// ❌ Before — new function reference every render
<FlatList
  renderItem={({ item }) => <ReelCard item={item} onLike={handleLike} />}
  keyExtractor={(item) => item.id.toString()}
/>

// ✅ After — stable references via useCallback
const renderItem = useCallback(
  ({ item }) => <ReelCard item={item} onLike={handleLike} />,
  [handleLike] // handleLike also wrapped in useCallback
);
const keyExtractor = useCallback(
  (item) => item.id.toString(), []
);

<FlatList renderItem={renderItem} keyExtractor={keyExtractor} />

Fix 2: React.memo on list item components

Even with a stable renderItem, child components re-render if they don't implement shouldComponentUpdate or React.memo. Wrapping ReelCard in React.memo with a custom comparator that only checks id and likeCount (not the full item object) cut render count by half on its own.

React.memo with custom comparator
const ReelCard = React.memo(
  ({ item, onLike }) => {
    // component implementation
  },
  (prev, next) =>
    prev.item.id === next.item.id &&
    prev.item.likeCount === next.item.likeCount
);

Fix 3: getItemLayout for fixed-height rows

For list screens with uniform item heights (notification list, followers list), getItemLayout tells FlatList the exact height without measuring. This eliminates the layout calculation pass and makes scrollToIndex instant. For Reels where each card is full screen height, this is trivial — CARD_HEIGHT = Dimensions.get('window').height.

getItemLayout — full-screen reels
const CARD_HEIGHT = Dimensions.get('window').height;

<FlatList
  getItemLayout={(_, index) => ({
    length: CARD_HEIGHT,
    offset: CARD_HEIGHT * index,
    index,
  })}
  windowSize={3}        // render 1 above + 1 below viewport
  maxToRenderPerBatch={2}
  initialNumToRender={1}
/>

Fix 4: Separate state that changes frequently

The typing indicator and unread badge count were stored in the same Redux slice as the message list. Any badge update triggered the entire ChatList to re-render. Moving ephemeral UI state (typing, badge) into a separate lightweight slice — and connecting only the header component to it — isolated re-renders to just the header.

After all fixes — memoised renderItem, React.memo with custom comparator, getItemLayout, windowSize tuning, and state isolation — Flipper showed a 40% reduction in re-renders. Average frame rate went from 45fps to a consistent 59–60fps on a mid-range Android test device.

Share X LinkedIn
NY
Neelesh Yadav
React Native Developer with 3 yr 6 mos years building production mobile apps. Writes about performance, architecture, and mobile engineering.
Follow ↗

Comments

Leave a comment

Comments are reviewed before they appear.

More Articles

Boosting React Native Performance: New Architectures Deep Dive
8 min read
Building Real-Time Chat with WebSocket in React Native
6 min read
AR Try-On: Three.js + MediaPipe in React Native
10 min read