javascriptreactjslistrerenderreact-usememo

How can I delete an item from list without re-rendering undeleted Items?


Thank you for all your comments and helpful remarks. following your advices, please find below a code sandbox link

https://codesandbox.io/s/magical-butterfly-uk0fjq?file=/src/item.js

which may help you figure out the issue. Everything is obvious in the console, with multiple logs, which, given the current scale of the example, does not pose any performance issue, but which could if there were a long list of items.

So the question is, how to proceed for preventing items which are not deleted from re-rendering, in spite of the fact that the parent (ItemList) re-renders.

The console shows the Item rendering.

As stated previously, I have used a useMemo + useCallback combination, but the result proved to be unstable.

Hope this example will help and be more explicit.

EDIT

regarding the console log, strangely, the sandbox example logs 2 times App, 2 times ItemList and 12 times Item, whereas on the computer it logs only 1-1-6 times


Solution

  • So, if you want to minimize the re-renders of the items, you need to make sure of a few things

    Memoize the component

    You can use React.memo on the component you want, which will prevent re-renders when the props/state do not change.

    so, use

    export default React.memo(Item);
    

    instead of

    export default Item;
    

    Make sure the props are the same

    When you render the Item component you pass deleteItem as a prop with a value that is received from the App component. However, this function is not a stable (it is redefined each time the App component renders. And since the App holds the items state, it will rerender after each deletion. This will trigger a new deleteItem to be defined, and that will cause the Item to re-render.

    To make this stable, you need two things.

    1. to use React.useCallback which re-uses the same function when its dependencies remain the same.
    2. use the function form of the setItems

    so, instead of

    const deleteItem = (newItem) => {
      const newItemList = items.filter(
        (item) => item.reference !== newItem.reference
      );
      setItems(newItemList);
    };
    

    use

    const deleteItem = React.useCallback((itemToDelete) => {
      setItems((currentItems) =>
        currentItems.filter((item) => item.reference !== itemToDelete.reference)
      );
    }, []);
    

    You also have an issue in your code, where you .map the data but then before returning each Item you push it in an array and return that instead. Just return the <Item ..>

    So instead of

        {props.data.map((item, index) => {
          const newList = [];
          newList.push(
            <Item
              deleteItem={props.deleteItem}
              key={item.reference}
              item={item}
            ></Item>
          );
          return newList;
        })}
    

    do

        {props.data.map((item, index) => {
          return (
            <Item
              deleteItem={props.deleteItem}
              key={item.reference}
              item={item}
            ></Item>
          );
        })}
    

    Updated codesandbox with all changes: https://codesandbox.io/s/twilight-water-9iyobx