← All shorts
37FlashList 1.6+2 min read

FlashList: Fixing FlatList Performance

FlashList recycles views instead of remounting them, fixing FlatList's blank-cell flicker and memory growth on long lists.

FlatList mounts a fresh component tree for every cell that scrolls into view and unmounts it on the way out. Mount, unmount, mount again. On complex rows that churn is expensive enough to leave blank cells behind during a fast scroll, and to push memory up steadily as you page deeper into a long feed.

#The old way

import { FlatList, View, Text, StyleSheet } from 'react-native';

type Message = { id: string; sender: string; body: string };

function MessageList({ messages }: { messages: Message[] }) {
  return (
    <FlatList
      data={messages}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={styles.row}>
          <Text style={styles.sender}>{item.sender}</Text>
          <Text numberOfLines={2}>{item.body}</Text>
        </View>
      )}
    />
  );
}

const styles = StyleSheet.create({
  row: { padding: 12, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: '#ddd' },
  sender: { fontWeight: '600', marginBottom: 4 },
});

#The new way

import { View, Text, StyleSheet } from 'react-native';
import { FlashList } from '@shopify/flash-list';

type Message = { id: string; sender: string; body: string };

function MessageList({ messages }: { messages: Message[] }) {
  return (
    <FlashList
      data={messages}
      keyExtractor={(item) => item.id}
      estimatedItemSize={72}
      renderItem={({ item }) => (
        <View style={styles.row}>
          <Text style={styles.sender}>{item.sender}</Text>
          <Text numberOfLines={2}>{item.body}</Text>
        </View>
      )}
    />
  );
}

const styles = StyleSheet.create({
  row: { padding: 12, borderBottomWidth: StyleSheet.hairlineWidth, borderColor: '#ddd' },
  sender: { fontWeight: '600', marginBottom: 4 },
});

The props read the same. renderItem and keyExtractor keep the shape they had under FlatList. What changed sits underneath, where FlashList hands an existing cell instance the new data instead of tearing the tree down and building a replacement.

#Why it matters

Memory stays roughly constant however far down the list you scroll, because only the visible cells plus a buffer ever exist as mounted components. The blank-cell flash goes away too. A reused cell never passes through an unmounted state, so there is no empty frame left to render.

Shopify's own benchmarks put blank area and JS thread time markedly below FlatList on large lists.

#Gotcha

FlashList sizes its recycling pool from estimatedItemSize before the first cell renders. Skip the prop and it guesses. That rough internal guess is what produces the visible jump or thrash on first render, plus a scroll position that refuses to settle until the list has re-measured everything itself.

So set it. Anything close to your real row height will do.

react-nativeperformancelists

Related shorts