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.
note
Always profile performance in the release mode. FlashList
's performance between JS dev and release mode differs greatly.
estimatedItemSize
Ensure estimatedItemSize
is as close as possible to the real average value - see here how to properly calculate the value for this prop.
Remove key
prop
warning
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
.
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
:
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 cirumstances, ensure that the key
is not tied to the item
prop in any way, so the keys don't change when recycling.
Let's imagine we want to display names of users:
const MyItem = ({ item }: { item: any }) => {
return (
<>
{item.users.map((user: any) => {
<Text key={user.id}>{user.name}</Text>;
})}
</>
);
};
If we wrote our item component like this, the Text
component would need to be re-created. Instead, we can do the following:
const MyItem = ({ item }) => {
return (
<>
{item.users.map((user, index) => {
/* eslint-disable-next-line react/no-array-index-key */
<Text key={index}>{user.name}</Text>;
})}
</>
);
};
Although using index as a key
in map
is not recommended by React, in this case since the data is derived from the list's data, the items will update correctly.
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 MyItem = ({ item }: { item: any }) => {
const MemoizedMyHeavyComponent = memo(MyHeavyComponent);
return (
<>
<MemoizedMyHeavyComponent />
<Text>{item.title}</Text>
</>
);
};