Reputation: 376
I've been having lots of trouble trying to avoid getting the "VirtualizedList: You have a large list that is slow to update" warning when using a <FlatList>
component with React-Native.
I've already done loads of research and Google-Fu to try a solution, but none of the solutions helped, not even the solution on this GitHub issue.
Things to keep in mind:
shouldComponentUpdate() { return false; }
in my List Item component, so they are not updating at all.PureComponent
renderItem
component is not being re-initialized on each call.Important: The warning only seems to occur after switching to a separate tab using my BottomTabNavigator
and then coming back and scrolling through my list; but this is confusing because when I do this, the FlatList screen component is not re-rendering and neither are the list items. Since the components aren't re-rendering when browsing to another tab, why would this error happen?
Here's my exact code:
App.tsx
const AppRoutes = [
{ name: "Home", Component: HomeScreen },
{ name: "Catalog", Component: CatalogScreen },
{ name: "Cart", Component: CartScreen }
];
export const StoreApp = () => {
return (
<NavigationContainer>
<StatusBar barStyle="dark-content" />
<Tabs.Navigator>
{AppRoutes.map((route, index) =>
<Tabs.Screen
key={index}
name={route.name}
component={route.Component}
options={{
headerShown: (route.name !== "Home") ?? false,
tabBarIcon: props => <TabIcon icon={route.name} {...props} />
}}
/>
)}
</Tabs.Navigator>
</NavigationContainer>
);
};
CatalogScreen.tsx
import React from "react";
import { FlatList, SafeAreaView, Text, View, StyleSheet } from "react-native";
import { LoadingSpinnerOverlay } from "../components/LoadingSpinnerOverlay";
import { getAllProducts, ProductListData } from "../api/catalog";
class ProductItem extends React.Component<{ item: ProductListData }> {
shouldComponentUpdate() {
return false;
}
render() {
return (
<View>
{console.log(`Rendered ${this.props.item.name}-${Math.random()}`)}
<Text style={{height: 100}}>{this.props.item.name}</Text>
</View>
);
}
}
export class CatalogScreen extends React.PureComponent {
state = {
productData: []
};
componentDidMount() {
getAllProducts()
.then(response => {
this.setState({ productData: response.data });
})
.catch(err => {
console.log(err);
});
}
private renderItem = (props: any) => <ProductItem {...props} />;
private keyExtractor = (product: any) => `${product.id}`;
private listItemLayout = (data: any, index: number) => ({
length: 100,
offset: 100 * index,
index
});
render() {
const { productData } = this.state;
console.log("CATALOG RENDERED");
return (
<SafeAreaView style={styles.pageView}>
{!productData.length && <LoadingSpinnerOverlay text="Loading products..." />}
<View style={{backgroundColor: "red", height: "50%"}}>
<FlatList
data={productData}
removeClippedSubviews
keyExtractor={this.keyExtractor}
renderItem={this.renderItem}
getItemLayout={this.listItemLayout}
/>
</View>
</SafeAreaView>
);
}
};
const styles = StyleSheet.create({
pageView: {
height: "100%",
position: "relative",
}
});
Since my components and lists are optimized and I'm still receiving the error, I'm starting to believe that this may be an actual issue with React Native - but if anyone can see what I'm doing wrong, or any workarounds, this would help greatly!
Additional Findings:
I found that the warning no longer occurs if the CatalogScreen
component is contained inside of a NativeStackNavigator
with a single Screen. I believe this may indicate that this is a problem with the BottomTabNavigator
module.
For example, the no warning no longer occurs if I make the following changes:
App.tsx
const AppRoutes = [
{ name: "Home", Component: HomeScreen },
{ name: "Catalog", Component: CatalogPage }, // Changed CatalogScreen to CatalogPage
{ name: "Cart", Component: CartScreen }
];
CatalogScreen.tsx
const Stack = createNativeStackNavigator();
export class CatalogPage extends React.PureComponent {
render() {
return (
<Stack.Navigator>
<Stack.Screen
name="CatalogStack"
options={{ headerShown: false }}
component={CatalogScreen}
/>
</Stack.Navigator>
);
}
}
With this workaround, I'm rendering the Stack Navigation component instead of the CatalogScreen
component directly. This resolves the problem, but I don't understand why this would make a difference. Does React Native handle memory objects differently in Stack Navigation screens as opposed to BottomTabNavigator screens?
Upvotes: 11
Views: 9616
Reputation: 2624
This is an old problem with FlatLists
in React Native. FlatList in React Native is not known for its performance. However this could be mitigated by checking for unnecessary re-renders or nested components within the FlatList and making sure that your FlatList is not nested by a ScrollView. But you already stated that you are not re-rendering the list when returning to the screen and it is not nested by a ScrollView so it's probably the component itself.
Honestly I recently have had the same problem as you, but my FlatList is more complex than yours. When running it on a real device I noticed I haven't had a problem, for me this was only an issue on Expo Go. I've done my share of research but I haven't hit the nail on the head so I can only offer suggetions.
Suggestion 1
Check out this RecyclerListView package, it seems very promising.
Suggestion 2
There is also this package, but I'm a bit skeptical about this one, I've heard complains React Native Optimized Flatlist package. I'm pretty sure I tried it and it did nothing for me.
Also, check out these two pages on the topic:
Upvotes: 3
Reputation:
I'm just going to take a random shot at an answer, but it's at best a guess.
The video you've linked in the comments to your question made it a lot more clear what's happening, something strange is going on there.
With normal lists like those, especially on mobile, you want to render and load only the items that's currently being displayed and visible, you don't want to keep the entire list of all possible items in memory and rendered all the time. That's why you'd use something like a callback to render the item, so that the list can invoke the callback as it's being scrolled and render only the items it needs to.
That being said here are a couple of random things I can think of:
({prop1, prop2})
instead of props
with ...props
. If you destructure the props like that it will create a new object every time an item is loaded. This could potentially be one of the culprits causing your issue, if the flatlist is constantly invoking the callback and creating tons of new objects. This could also potentially be an issue for the product item, if it sees that it's received a new prop reference, which means it's a different object, then it will have to do a shallow comparison (even if it's a pure component), on hundreds of props. That could really slow it down. The result is that it won't actually rerender it but it will still do hundreds of shallow object comparisons which is a slow process. So fix the destructuring, I'd bet something like this is causing the performance issue.this
.If all else fails I'd suggest you ask react native on github and in the meantime try using an alternative if there is one. On the other hand I don't really see any performance issues from the video, so perhaps it's also an error that can be safely ignored.
Upvotes: 1