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?
Use simple components Try to avoid a lot of logic and nesting in your list items. If you are reusing this list item component a lot in your app, create a duplicate just for your big lists and make them with less logic as possible and less nested as possible.
The difference between them is that React. Component doesn't implement shouldComponentUpdate() , but React. PureComponent implements it with a shallow prop and state comparison. If your React component's render() function renders the same result given the same props and state, you can use React.
Virtualization massively improves memory consumption and performance of large lists by maintaining a finite render window of active items and replacing all items outside of the render window with appropriately sized blank space.
The shouldComponentUpdate method allows us to exit the complex react update life cycle to avoid calling it again and again on every re-render. It only updates the component if the props passed to it changes.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With