FlashList use reusable component while scrolling

852 Views Asked by At

isActive state is not updated when scroll down little bit FlashList copies previous component (it does not create from scratch for each scrolling). I don't want to update isActive state with inside useEffect, because it causes performance issue. Also, I have to send and store isSelected prop like that way. How to solve this problem? Could you suggest alternative solution about that?

I am expecting that if isSelected value is true container background color should be red else gray.

My dummy data is sth like that:

const data = new Array(50).fill(false);
data[1] = true;

Here is my demo code:

import {StyleSheet, View} from 'react-native';
import React, {useState} from 'react';
import {FlashList} from '@shopify/flash-list';

interface CustomTestInterface {
  isSelected: boolean;
}
const CustomTest = ({isSelected}: CustomTestInterface) => {
  const [isActive, setIsActive] = useState<boolean>(isSelected);

  return (
    <View
      style={{
        height: 80,
        width: '100%',
        backgroundColor: isActive ? 'red' : 'gray',
        marginTop: 10,
      }}
    />
  );
};
const Test = () => {
  return (
    <View style={{flex: 1, backgroundColor: 'green'}}>
      <FlashList
        data={data}
        estimatedItemSize={80}
        renderItem={({item}) => <CustomTest isSelected={item} />}
      />
    </View>
  );
};

export default Test;

const styles = StyleSheet.create({});

1

There are 1 best solutions below

2
PhantomSpooks On

FlashList is fast because it reuses components rather than destroying and creating new ones. Because of this, it keeps the state of the previous component, so you'll have to update the state when the component is recycled:

import { StyleSheet, View, Text } from 'react-native';
import React, { useState, useRef } from 'react';
import { FlashList } from '@shopify/flash-list';

// add id to help keep track of component
const data = new Array(50).fill(null).map((_, i) => ({
  id: i + 1,
  isSelected: false,
}));
data[1].isSelected = true;
// made CustomTest props match flashlist's renderItem props
interface CustomTestInterface {
  item: {
    isSelected: boolean;
    id: number;
  };
  index: number;
}
const CustomTest = (props: CustomTestInterface) => {
  const { isSelected, id } = props.item;
  const lastItemId = useRef(id);
  const [isActive, setIsActive] = useState<boolean>(isSelected);
  if (id !== lastItemId.current) {
    console.log(`${lastItemId.current} has been recycled to ${id}`);
    lastItemId.current = id;
    setIsActive(isSelected);
  }
  return (
    <View
      style={{
        height: 80,
        width: '100%',
        backgroundColor: isActive ? 'red' : 'gray',
        marginTop: 10,
      }}>
      <Text>{id}</Text>
    </View>
  );
};
const Test = () => {
  return (
    <View style={{ flex: 1, backgroundColor: 'green' }}>
      <FlashList
        data={data}
        estimatedItemSize={80}
        renderItem={(props) => <CustomTest {...props} />}
      />
    </View>
  );
};

export default Test;
const styles = StyleSheet.create({});

Demo