question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ListFooterComponent prop raises performance warning if it's dynamic.

See original GitHub issue

Description

After few hours of debugging the FlatList/VirtualizedList performance warning:

VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc

I have found that ListFooterComponent is causing the issue if we pass a dynamic value to it (e.g: ListFooterComponent={isLoading && <Footer />}). If we pass a static value (e.g: ListFooterComponent={<Footer />}, the warning will never appear.

Version

0.66.0

Output of npx react-native info

System: OS: macOS 11.6 CPU: Intel® Core i7 CPU @ 2.50GHz Memory: 16.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 14.17.6 - ~/.nvm/versions/node/v14.17.6/bin/node Yarn: 1.22.11 - /usr/local/bin/yarn npm: 6.14.15 - ~/.nvm/versions/node/v14.17.6/bin/npm Watchman: 2021.09.06.00 - /usr/local/bin/watchman Managers: CocoaPods: 1.11.2 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 21.0.1, iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0 IDEs: Xcode: 13.1/13A1030d - /usr/bin/xcodebuild npmPackages: @react-native-community/cli: Not Found react: 17.0.2 => 17.0.2 react-native: 0.66.0 => 0.66.0 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

Steps to reproduce

  1. Create a React Native app.
  2. Use the code example in the new React Native app.
  3. Build and launch the app.
  4. Keep scrolling until no more data can be loaded.
  5. Try to scroll up and the performance warning will appear right away, even that the list is not large (20 items).

ListFooterComponent

Snack, code example, screenshot, or link to a repository

// App.tsx
import React from 'react';
import { SafeAreaView } from 'react-native';
import List from './List';

const App = () => {
    return (
        <SafeAreaView style={{ flex: 1 }}>
            <List />
        </SafeAreaView>
    );
};

export default App;
// List.tsx

import React, { memo, useCallback, useEffect, useState } from 'react';
import {
    ActivityIndicator,
    ListRenderItem,
    StyleSheet,
    Text,
    View,
    VirtualizedList,
} from 'react-native';

const data = [
    { id: 1, name: 'Abarth' },
    { id: 2, name: 'Alfa Romeo' },
    { id: 3, name: 'Aston Martin' },
    { id: 4, name: 'Audi' },
    { id: 5, name: 'Audi Sport' },
    { id: 6, name: 'BAIC Motor' },
    { id: 7, name: 'BeiBen' },
    { id: 8, name: 'Bentley' },
    { id: 9, name: 'Berkeley' },
    { id: 10, name: 'BharatBenz' },
    { id: 11, name: 'Bizzarrini' },
    { id: 12, name: 'BMW' },
    { id: 13, name: 'Brabus' },
    { id: 14, name: 'Bugatti' },
    { id: 15, name: 'Cadillac' },
    { id: 16, name: 'Chevrolet' },
    { id: 17, name: 'Chrysler' },
    { id: 18, name: 'Corre La Licorne' },
    { id: 19, name: 'Dacia' },
    { id: 20, name: 'Daewoo' },
];

const getData = (from: number) => {
    if (from > data.length) {
        return [];
    }

    return data.filter((_, i) => i >= from && i < from + 5);
};

type ItemProps = {
    name: string;
};

const ListItem = memo(({ name }: ItemProps) => {
    return (
        <View style={styles.itemContainer}>
            <Text style={styles.itemText}>{name}</Text>
        </View>
    );
});

const Footer = () => {
    return (
        <View style={styles.footerContainer}>
            <ActivityIndicator size={'large'} />
        </View>
    );
};

const List = () => {
    const [isLoading, setIsLoading] = useState(false);
    const [currentOffset, setCurrentOffset] = useState(0);
    const [items, setItems] = useState<ItemProps[]>([]);

    const getItemCount = useCallback((_data: ItemProps[]) => {
        return _data.length;
    }, []);

    const getItem = useCallback((_data: ItemProps[], index: number) => {
        return _data[index];
    }, []);

    const extractListKeys = useCallback(item => {
        return `${item.id}`;
    }, []);

    const renderItem: ListRenderItem<ItemProps> = useCallback(({ item }) => {
        return <ListItem name={item.name} />;
    }, []);

    const loadMoreData = useCallback(async () => {
        if (isLoading) {
            return;
        }

        setIsLoading(true);

        await new Promise(resolve => setTimeout(resolve, 1500));

        const _data = getData(currentOffset);

        if (_data.length > 0) {
            setItems(prevItems => [...prevItems, ..._data]);
        }

        setIsLoading(false);
        setCurrentOffset(prevOffset => prevOffset + 5);
    }, [currentOffset, isLoading]);

    useEffect(() => {
        loadMoreData();

        // I don't want to add loadMoreData to the useEffect dependencies
        // for the sake of this example.
        // Adding it will create an infinite call to loadMoreData since it
        // has dependencies (isLoading and currentOffset) that change a lot.
    }, []);

    return (
        <View style={styles.screen}>
            <View style={styles.topView}>
                <Text style={styles.topViewText}>
                    {`currentOffset: ${currentOffset}, isLoading: ${
                        isLoading ? 'Yes' : 'No'
                    }`}
                </Text>
            </View>

            <VirtualizedList
                data={items}
                initialNumToRender={4}
                style={styles.list}
                renderItem={renderItem}
                getItem={getItem}
                getItemCount={getItemCount}
                keyExtractor={extractListKeys}
                ListFooterComponent={isLoading && <Footer />}
                onEndReached={loadMoreData}
            />
        </View>
    );
};

const styles = StyleSheet.create({
    screen: {
        flex: 1,
    },
    list: {
        flex: 1,
    },
    itemContainer: {
        height: 230,
        justifyContent: 'center',
        alignItems: 'center',
        borderBottomWidth: 1,
    },
    itemText: {
        fontSize: 20,
    },
    topView: {
        padding: 14,
        backgroundColor: '#999999',
    },
    topViewText: {
        color: '#000000',
        fontSize: 18,
    },
    footerContainer: {
        alignItems: 'center',
        padding: 20,
    },
});

export default List;

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:5

github_iconTop GitHub Comments

4reactions
Chandra-Panta-Chhetricommented, Jan 21, 2022

I am also facing the same issue with react-native version 0.64.3.

0reactions
github-actions[bot]commented, Jul 31, 2022

This issue was closed because it has been stalled for 7 days with no activity.

Read more comments on GitHub >

github_iconTop Results From Across the Web

React-Native another VirtualizedList-backed container
Solution #2. If you use FlatList inside the ScrollView it gives warning which is annoying, so you can use array's map property, like...
Read more >
A deep dive into React Native FlatList - LogRocket Blog
FlatList's data property is a required prop which accepts an array of items that we want to display onto the screen. Here is...
Read more >
Creating Lists - You.i TV Developer Portal
If children of your List aren't marked as placeholder items, loading them results in ... Like a Facebook React Native FlatList, there are...
Read more >
Usage | FlashList
If your list has heterogenous views, pass their types to FlashList using getItemType prop to improve performance. Do not test performance with ...
Read more >
8 ways to optimize React native FlatList performance
It's better if you avoid arrow functions inline for all the props of FlatList like keyExtractor , ListHeaderComponent , ListFooterComponent etc.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found