universe11
universe11

Reputation: 1053

How can I rerender only one item in a flatlist?

I have products with a star icon to add this product in wishlist. I map 10 list of products and each map has 3 products like:

(I Map it in Pagerview to swipe to the next products)

Products Component

const ListProducts = [
  {
    id: 1,
    products: [{
      product_id: 1,
      photos: [...]
    }]
  },
  {
    id: 2,
    products: [{
      product_id: 1,
      photos: [...]
    }]
  }
  {
    id: 3,
    products: [{
      product_id: 1,
      photos: [...]
    }]
  },
  {
    id: 4,
    products: [{
      product_id: 1,
      photos: [...]
    }]
  }
...
];


function isEq(prev, next) {
  if(prev.is_wishlist === next.is_wishlist) {
    return true;
  }
}

const Item = memo(({ id, photos, is_wishlist, onPress, onPressWishlist }) => {
  const findProductIdInWishList = is_wishlist.find((el => el.product_id === id));
  return (
    <Button style={s.imgBtn} onPress={() => onPress(id)}>
      <Button onPress={() => onPressWishlist(id)} style={s.starBtn}>
        <AntDesign name={findProductIdInWishList ? 'star' : 'staro'} size={20} color={globalStyles.globalColor} />
      </Button>
      <ImageSlider photos={photos} />
    </Button>
  )
  // @ts-ignore
}, isEq);

  const wishlist = useSelector((state) => state.WishList.wishlist);
  const dispatch = useDispatch();

  const renderItem: ListRenderItem<IProduct> = ({ item }) => (
    <Item
      id={item.id}
      photos={item.photos}
      is_wishlist={wishlist}
      onPressWishlist={handlePressWishList}
    />
  )


  const handlePressWishList = (product_id: string) => {
    dispatch(addAndRemoveProductToWishList({product_id}));
  };

List of Products component

Products Map:

      <PagerView onPageSelected={(e) => handleSetAllIndexes(e.nativeEvent.position)} style={s.container} initialPage={index}>
      {
        allProducts.map((el, i) => (
          <View style={s.viewsContainer} key={i}>
            { allIndex.includes(i) ? (
              <View style={s.viewsInnerContainer}>
                { /* products */ }
                <Products products={el.products as IProduct[]} total_price={el.total_price} product_name={el.packet_name} />
              </View>
              ) : (
              <View style={s.loadingContainer}>
                <Loader size={'large'} color='#fff' />
              </View>
              )
            }
          </View>)
        )
      }
      </PagerView>

if I click on star icon its dispatch and it goes fast but if I swipe to other products maybe to the last, then I press the star icon to dispatch then its a delay/lag you can see it

I dont add the full code because there are some snippets that has nothing to do with problem.

PS:

Video

Upvotes: 7

Views: 2423

Answers (3)

zzz
zzz

Reputation: 1105

I think there are a few issues in your code:

1. Wrong dependency list for useMemo.

In your Item component, you should pass the list of dependency, rather than a compare function:

const Item = memo(({ id, photos, is_wishlist, onPress, onPressWishlist }) => {
  ...
  // @ts-ignore
}, isEq);  // <- this is wrong

// Instead, you should do this:
}, [is_wishlist]);  // <- this is correct, if you only want to update Item component when `is_wishlist` is changed

2. Never use index as key if item can be reordered

In your products maps component, you are doing:

allProducts.map((el, i) => (
  <View style={s.viewsContainer} key={i}>

You should pass id instead, so React will not re-render all items when you insert a new item at the beginning or in the middle:

<View style={s.viewsContainer} key={el.id}>

3. Passing wishlist to all Items, however, wishlist will be updated whenever user click star button on any item.

This causes all Item to re-generate memoized component, because wishlist is changed.

What you want to do here is only passing essential data to Item, which is inWishList (or findProductIdInWishList in your code), which only get changed for affected item:

const renderItem: ListRenderItem<IProduct> = ({ item }) => {
  const inWishList= wishlist.find((el => el.product_id === id));
  return (
    <Item
      id={item.id}
      photos={item.photos}
      inWishList={inWishList}
      onPressWishlist={handlePressWishList}
    />
  )
}

Upvotes: 1

Jiř&#237; Petera
Jiř&#237; Petera

Reputation: 432

The issue is connected to a way how you edit your data to hold the new value. It looks like the act of adding an item to a wishlist causes all the previous items to re-render. Therefore the first one works without an issue, but the last one takes a while as it needs to re-render all the other items before. I would start by changing the key from index to an actual ID of a product block since that could prevent the other "pages" from re-rendering.

If that fails you will probably need to take this block of code into a workshop and check for useless re-renders.

Upvotes: 0

Four
Four

Reputation: 1084

I am going to edit my answer instead of comment. Before my code, let me explain first. In your current code, whenever 'allProducts' changes, everything will re-render. Whenever 'allIndex' changes, everything will re-render too. The longer the list, the more lag it will be.
Can you try 'useCallback' in this case?

const renderItem = React.useCallback((el, i) => (
  <View style={s.viewsContainer} key={i}>
      {allIndex.includes(i) ? (
        <View style={s.viewsInnerContainer}>
          <Products />
        </View>
      ) : (
        <Loading />
      )}
    </View>
),[allIndex])

{allProducts.map(renderItem)}

Now, renderItem will re-render when 'allIndex' changes. Instead of 'PagerView', I still recommend 'FlatList' with 'horizontal={true}' and 'some styles'. If still wanna use 'PagerView', how about 'Lazy' components? 'Lazy components' does not render the components before they came into user view. So, before they are seen, they do not take part in re-redner.

Upvotes: 1

Related Questions