最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - "VirtualizedList: You have a large list that is slow to update" warning even though all component

programmeradmin0浏览0评论

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> ponent 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:

  • I'm using shouldComponentUpdate() { return false; } in my List Item ponent, so they are not updating at all.
  • The ponent that renders the FlatList is already a PureComponent
  • I added console.logs in the render methods of my ponents and can confirm they don't re-render.
  • I'm not using anonymous functions, so the renderItem ponent 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 ing back and scrolling through my list; but this is confusing because when I do this, the FlatList screen ponent is not re-rendering and neither are the list items. Since the ponents 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}
            ponent={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 "../ponents/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: []
  };
  
  ponentDidMount() {
    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 ponents 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 ponent 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 }}
          ponent={CatalogScreen}
        />
      </Stack.Navigator>
    );
  }
}

With this workaround, I'm rendering the Stack Navigation ponent instead of the CatalogScreen ponent 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?

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> ponent 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:

  • I'm using shouldComponentUpdate() { return false; } in my List Item ponent, so they are not updating at all.
  • The ponent that renders the FlatList is already a PureComponent
  • I added console.logs in the render methods of my ponents and can confirm they don't re-render.
  • I'm not using anonymous functions, so the renderItem ponent 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 ing back and scrolling through my list; but this is confusing because when I do this, the FlatList screen ponent is not re-rendering and neither are the list items. Since the ponents 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}
            ponent={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 "../ponents/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: []
  };
  
  ponentDidMount() {
    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 ponents 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 ponent 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 }}
          ponent={CatalogScreen}
        />
      </Stack.Navigator>
    );
  }
}

With this workaround, I'm rendering the Stack Navigation ponent instead of the CatalogScreen ponent 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?

Share Improve this question edited Nov 29, 2021 at 23:56 Jordan Arldt asked Nov 22, 2021 at 0:11 Jordan ArldtJordan Arldt 3762 silver badges11 bronze badges 7
  • How is your "CATALOG RENDERED" not logging twice, from what I can see it has to render twice, you're rendering everything, then triggering a state update after the ponent has been mounted, which should trigger another render. The fact that you say it doesn't render twice is an issue, the only reason it wouldn't render twice is if it's catching an error and logging it instead of updating the product state. – user16435030 Commented Nov 28, 2021 at 19:18
  • Is there any improvement if you add the getItemLayout to the FlatList ponent? Or your ponents are of variable dimensions? – joseluismurillorios Commented Nov 29, 2021 at 2:36
  • @Matriarx I apologize, yes, it renders once initially and renders again after the API data is fetched - however, it does not render again at any point after that, even when switching tabs on my BottomTabNavigator. – Jordan Arldt Commented Nov 29, 2021 at 20:10
  • @joseluismurillorios I appreciate the idea! I actually did try this after making this post and it didn't make a difference. Each list item is 100 points in height. What's weird is I only get the error after switching to another tab, ing back, and then start scrolling on the list. – Jordan Arldt Commented Nov 29, 2021 at 20:11
  • It is weird that it only happens after switching tabs, because then my instinct is to say it's becasue of rendering again. But you say it doesn't render again after you've switched tabs, which is strange. Try making your ponents all pure views and if you can use memo on the parent, see if that helps. If something is triggering a render, even if it's not showing in console, perhaps pure/memo will prevent it. – user16435030 Commented Nov 29, 2021 at 21:12
 |  Show 2 more ments

2 Answers 2

Reset to default 3

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 ponents 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 ponent itself.

Honestly I recently have had the same problem as you, but my FlatList is more plex 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 plains 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:

  • FlatList Performance Tips
  • GitHub Issue #13413

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 ments 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:

  • Make sure that every single ponent, including and especially the item ponent, are pure ponents so they don't rerender. Make really sure that item ponent isn't rendering again.
  • Try to alter your props destructure so that you directly receive the props, i.e. ({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 parison (even if it's a pure ponent), 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 parisons which is a slow process. So fix the destructuring, I'd bet something like this is causing the performance issue.
  • Make sure that you don't double render the view, don't render the flatlist before the data is actually loaded, before the state has been set only return the fallback spinner or loader and once the state has been set then return the full child. Otherwise you're double rendering the whole thing, even if there's no data.
  • Try and see if it makes a difference if you use an anonymous function as the renderItem callback, instead of using react class functions. I don't think it should make a difference but it's worth a shot if nothing else helps, since I'm not sure how the renderItem is utilizing 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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论