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>
);
};
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,