Home > database >  React Native: whole FlatList rerenders on every change, even with React.memo
React Native: whole FlatList rerenders on every change, even with React.memo

Time:11-06

I've been struggling with this issue for the past couple of days: I have a list of creators stored in an array, along with another array for followed creators, but when the user 'follows' a creator the whole FlatList re-renders. This is an issue because I'm loading 3 images for each creator, so there's a lot of flickering and lag every time the user follows/unfollows a creator.

I've tried using React.memo but it doesn't seem to be working and I feel like there may be issues in other parts of my code as well. Any help would be greatly appreciated!

Getting data on first render:

  useMemo(() => {
    setRefreshing(true);
    return onValue(ref(db, '/users/'   auth?.currentUser?.uid   '/vendorsFollowing'), async (querySnapShot) => {
      let data = (await querySnapShot.val()) || {};
      let vendorData = { ...data };
      setVendorsFollowing(Object.keys(vendorData))
      setRefreshing(false);
    });
  }, []);

  useMemo(() => {
    setRefreshing(true);
    return onValue(ref(db, '/users'), (querySnapShot) => {
      let data = querySnapShot.val() || {};
      let vendorList = { ...data };
      setVendorArray(vendorList);
      setFilteredVendorArray(vendorList);
      setRefreshing(false);
    });
  }, []);

VendorItem:

const VendorItem = React.memo(({ vendor }: any) => {
    // image stuff
    const [imgUrl1, setImgUrl1] = useState<string | undefined>(undefined);
    const ref1 = ref_storage(storage, vendor.uid   '_1.png');

    const [imgUrl2, setImgUrl2] = useState<string | undefined>(undefined);
    const ref2 = ref_storage(storage, vendor.uid   '_2.png');

    const [imgUrl3, setImgUrl3] = useState<string | undefined>(undefined);
    const ref3 = ref_storage(storage, vendor.uid   '_3.png');

    if (!blocked) {
      getDownloadURL(ref1)
        .then((url) => {
          setImgUrl1(url);
        })
        .catch((error) => {
        });

      getDownloadURL(ref2)
        .then((url) => {
          setImgUrl2(url);
        })
        .catch((error) => {
        });

      getDownloadURL(ref3)
        .then((url) => {
          setImgUrl3(url);
        })
        .catch((error) => {
        });
    }

return(
   <View style={[styles.eventImageContainer, { marginVertical: (imgUrl1 || imgUrl2 || 
   imgUrl3) && 5 }]}>
          {imgUrl1 && <Image source={{ uri: imgUrl1 }} style={styles.vendorImage} 
   imageStyle={{ borderRadius: 20 }} />}
          {imgUrl2 && <Image source={{ uri: imgUrl2 }} style={styles.vendorImage} 
   imageStyle={{ borderRadius: 20 }} />}
          {imgUrl3 && <Image source={{ uri: imgUrl3 }} style={styles.vendorImage} 
   imageStyle={{ borderRadius: 20 }} />}
        </View>

   // updateStarred basically adds vendor.uid to vendorsFollowing and to the database
   <TouchableOpacity onPress={() => { updateStarred(vendor.uid) }}>
      <Icon
         name={vendorsFollowing.includes(vendor.uid) ? 'bookmark' : 'bookmark-o'}
         size={25}
         color="white"
      />
   </TouchableOpacity>
)
}, (prevProps, nextProps) => {
   if (vendorsFollowing.includes(prevProps.vendor.uid) === vendorsFollowing.includes(nextProps.vendor.uid)) return true;
    return false;
  });

Edit: FlatList:

<FlatList
            initialNumToRender={7}
            contentContainerStyle={{ paddingBottom: 325 }}
            showsVerticalScrollIndicator={false}
            style={{ marginTop: 10 }}
            data={Object.keys(filteredVendorArray)}
            keyExtractor={(item) => filteredVendorArray[item].uid}
            refreshControl={<RefreshControl refreshing={refreshing} onRefresh={loadNewData} />}
            renderItem={({ item }) => (
              <View style={{ backgroundColor: '#FFfF8F3', marginBottom: 20 }}>
                <VendorItem vendor={filteredVendorArray[item]} />
              </View>
            )}/>

CodePudding user response:

As a renderItem property, you provide an anonymous function which is always created and trigger a new render. Replace it:

renderItem={({ item }) => (
<View style={{ backgroundColor: '#FFfF8F3', marginBottom: 20 }}>
  <VendorItem vendor={filteredVendorArray[item]} />
</View>
)}

with:

const renderItemFn = useCallback(() => ({ item }) => (
<VendorItem vendor={filteredVendorArray[item]} />
))
...
renderItem={renderItemFn}

The same problem in keyExtractor.

Also move View View style={{ backgroundColor: '#FFfF8F3', marginBottom: 20 }} to VendorItem because it doesn't have memorization

More examples of flatlist optimizations here: https://www.obytes.com/blog/a-guide-to-optimizing-flatlists-in-react-native

Yet another possible problem is your memoization, if vendor is a complex object you need to add a custom compare function:

const customComparator = (prevProps, nextProps) => {
  // add your logic for comprassion
  return nextProps. vendor === prevProps. vendor;
};

export default React.memo(VendorItem, customComparator);
  • Related