Performance
Profiling
Before assessing your list's performance, make sure you are in release mode. On Android, you can disable JS dev mode inside the developer menu, whereas you need to run the release configuration on iOS. FlashList can appear to be slower than FlatList in dev mode. The primary reason is a much smaller and fixed window size equivalent. Click here to know more about why you shouldn't profile with dev mode on.
Memoizing props passed to FlashList is more important in v2. v1 was more selective about updating items, but this was often perceived as a bug by developers. We will not follow that approach and will instead allow developers to ensure that props are memoized. We will stop re-renders of children wherever it is obvious.
Writing Performant Components
While FlashList
does its best to achieve high performance, it will still perform poorly if your item components are slow to render. In this post, let's dive deeper into how you can remedy this.
Recycling
One important thing to understand is how FlashList
works under the hood. When an item gets out of the viewport, instead of being destroyed, the component is re-rendered with a different item
prop. When optimizing your item component, try to ensure as few things as possible have to be re-rendered and recomputed when recycling.
Optimizations
There's lots of optimizations that are applicable for any React Native component and which might help render times of your item components as well. Usage of useCallback
, useMemo
, and useRef
is advised - but don't use these blindly, always measure the performance before and after making your changes.
Always profile performance in the release mode. FlashList
's performance between JS dev and release mode differs greatly.
Remove key
prop
Using key
prop inside your item and item's nested components will highly degrade performance.
Make sure your item components and their nested components don't have a key
prop. Using this prop will lead to FlashList
not being able to recycle views, losing all the benefits of using it over FlatList
.
Why are keys harmful to FlashList?
FlashList's core performance advantage comes from recycling components instead of creating and destroying them however, when you add a key
prop that changes between different data items, React treats the component as entirely different and forces a complete re-creation of the component tree.
For example, if we had a following item component:
const MyNestedComponent = ({ item }) => {
return <Text key={item.id}>I am nested!</Text>;
};
const MyItem = ({ item }) => {
return (
<View key={item.id}>
<MyNestedComponent item={item} />
<Text>{item.title}</Text>
</View>
);
};
Then the key
prop should be removed from both MyItem
and MyNestedComponent
. It isn't needed and react can alredy take care of updating the components.
const MyNestedComponent = ({ item }) => {
return <Text>I am nested!</Text>;
};
const MyItem = ({ item }) => {
return (
<View>
<MyNestedComponent item={item} />
<Text>{item.title}</Text>
</View>
);
};
There might be cases where React forces you to use key
prop, such as when using map
. In such circumstances, use useMappingHelper
to ensure optimal performance:
import { useMappingHelper } from "@shopify/flash-list";
const MyItem = ({ item }) => {
const { getMappingKey } = useMappingHelper();
return (
<>
{item.users.map((user, index) => (
<Text key={getMappingKey(user.id, index)}>{user.name}</Text>
))}
</>
);
};
The useMappingHelper
hook intelligently provides the right key strategy:
- When inside FlashList: Uses stable keys that don't change during recycling
- When outside FlashList: Uses the provided item key for proper React reconciliation
This approach ensures that:
- Components can be recycled properly within FlashList
- React's reconciliation works correctly
- Performance remains optimal
useMappingHelper
should be used whenever you need to map over arrays inside FlashList item components. It automatically handles the complexity of providing recycling-friendly keys.
Difficult calculations
If you do any calculations that might take a lot of resources, consider memoizing it, making it faster, or removing it altogether. The render method of items should be as efficient as possible:
getItemType
If you have different types of cell components and these are vastly different, consider leveraging the getItemType
prop. For example, if we were building a messages list, we could write it like this:
// A message can be either a text or an image
enum MessageType {
Text,
Image,
}
interface TextMessage {
text: string;
type: MessageType.Text;
}
interface ImageMessage {
image: ImageSourcePropType;
type: MessageType.Image;
}
type Message = ImageMessage | TextMessage;
const MessageItem = ({ item }: { item: Message }) => {
switch (item.type) {
case MessageType.Text:
return <Text>{item.text}</Text>;
case MessageType.Image:
return <Image source={item.image} />;
}
};
// Rendering the actual messages list
const MessageList = () => {
return <FlashList renderItem={MessageItem} estimatedItemSize={200} />;
};
However, this implementation has one performance drawback. When the list recycles items and the MessageType
changes from Text
to Image
or vice versa, React won't be able to optimize the re-render since the whole render tree of the item component changes. We can fix this by changing the MessageList
to this:
const MessageList = () => {
return (
<FlashList
renderItem={MessageItem}
estimatedItemSize={200}
getItemType={(item) => {
return item.type;
}}
/>
);
};
FlashList
will now use separate recycling pools based on item.type
. That means we will never recycle items of different types, making the re-render faster.
Leaf components
Let's consider the following example:
const MyHeavyComponent = () => {
return ...;
};
const MyItem = ({ item }) => {
return (
<>
<MyHeavyComponent />
<Text>{item.title}</Text>
</>
);
};
Since MyHeavyComponent
does not directly depend on the item
prop, memo
can be used to skip re-rending MyHeavyComponent
when the item is recycled and thus re-rendered:
const MyHeavyComponent = () => {
return ...;
};
const MemoizedMyHeavyComponent = memo(MyHeavyComponent);
const MyItem = ({ item }: { item: any }) => {
return (
<>
<MemoizedMyHeavyComponent />
<Text>{item.title}</Text>
</>
);
};