Search code examples
typescriptreact-nativereact-functional-component

Section list item update renders all the section list items


I have a section list that shows a list of items, each item is a component itself and contains a checkbox and some other views. The checkbox is not visible at first. long pressing on any of the items will make the checkboxes visible.

The issue

If I toggle any checkboxes for a single item, all the items in the section list rerender. thus, making the app lag a little bit.

I want to be able only to rerender the updated item.

My Screen

export const MyScreen: React.FC<Props> = ({}) => {

  const [itemSelectionMode, setItemSelectionMode] = useState<boolean>(false);
  const [selectedItemIDs, setSelectedItemIDs] = useState<number[]>([]);

  // I am using RTK-Query
  const {
    data: events,
    error,
    isFetching,
  } = useGetItemsQuery({
    date: someDate,
  });

  const onItemCheckBoxValueChange = (
    selected: boolean,
    itemID: number,
  ): void => {
    // toggle selected event in events.result.schedules
    if (selected) {
      setSelectedItemIDs([...selectedItemIDs, itemID]);
    } else {
      setSelectedItemIDs(selectedItemIDs.filter(id => id !== itemID));
    }
  };

  const onItemCardLongPress = (): void => {
    setItemSelectionMode(true);
  };

 const renderListItem = (item: Item): JSX.Element => {
    return (
      <ListItem
        item={item}
        onLongPress={onItemCardLongPress}
        selectionMode={eventSelectionMode}
        onCheckboxValueChange={onItemCheckBoxValueChange}
        selected={selectedItemIDs.includes(item.id)}
      />
    );
  };

 const renderItemList = (): JSX.Element => {
    return (
      <SectionList
        sections={items}
        renderItem={({ item }) => renderListItem(item)}
        renderSectionHeader={({ section: { title } }) => <Text>{title}</Text>}
      />
    );
  };

return (
    <View style={style.mainContainer}>
      {renderEventList()}
    </View>
);

}

ListItem.tsx

const renderItemDetails = (details: string) => {
  return <Text style={style.details}>{details}</Text>;
};

const renderItemAddress = (address: string) => {
  if (!address) return null;
  return <Text style={style.address}>{address}</Text>;
};

export const EventListItem: React.FC<EventListItemProps> = ({
  item,
  onLongPress,
  selectionMode = false,
  onCheckboxValueChange,
  selected = false,
}) => {
  const address = someUtilFunction(item.address);
  const details = someUtilFunction(item.boxes);

  return (
    <Card style={style.card} key={item.id} onLongPress={onLongPress}>
      // some other views
      <View style={style.headerContainer}>
        {selectionMode && (
          <Checkbox
            value={selected}
            onValueChange={(value) => {
              onCheckboxValueChange(value, item.id);
            }}
          />
        )}
      </View>
      {renderItemAddress(address)} 
      {renderItemDetails(details)}
      // some other views
    </Card>
  );
};

Solution

  • As @Nutty suggested I had to change ListItem.tsx to

    export const EventListItem: React.FC<EventListItemProps> = React.memo(
     ({
      item,
      onLongPress,
      selectionMode = false,
      onCheckboxValueChange,
      selected = false,
    }) => {
    

    as per react documentation

    memo lets you skip re-rendering a component when its props are unchanged.

    since react uses shallow comparison to detect whether the props have changed or not, and I am passing two callback functions onLongPress and onCheckboxValueChange as props for the ListItem.tsx. I have to use useCallback hook on the above functions onLongPress and onCheckboxValueChange to result as equal in shallow comparison.

    after adding the call backs the onItemCardLongPress will look like

    const onItemCardLongPress = useCallback((): void => {
        setEventSelectionMode(true);
      }, [events]);
    

    and the onItemCheckBoxValueChange will look like

    const onItemCheckBoxValueChange = useCallback(
        (
        selected: boolean,
        itemID: number,
      ): void => {
        // toggle selected event in events.result.schedules
        if (selected) {
          setSelectedItemIDs([...selectedItemIDs, itemID]);
        } else {
          setSelectedItemIDs(selectedItemIDs.filter(id => id !== itemID));
        }
      },
        [events]
      );
    

    I have found some useful topics during my endeavor,

    Be More Functional with React.memo + TypeScript

    Getting Started with React.memo()