Skipperlla / rn-swiper-list

⚡ Lightning fast and customizable tinder-like swiper for React Native
https://www.npmjs.com/package/rn-swiper-list
MIT License
150 stars 15 forks source link

Avoid rendering all cards (performance) #36

Open tilap opened 1 month ago

tilap commented 1 month ago

When mounting , it process renderCard over all the item we provide in data.

We quickly encounter performance issues (and even app crashs) if we have a lot of data (a lot = 100 or above).

It would be great to have an option to avoid rendering cards depending on the current active index for example.

Skipperlla commented 1 month ago

I have a development on this, but I can't add it right now because I don't have the time, but I will do it as soon as possible.

tilap commented 1 month ago

I forked locally and succeed in having 10.000 items by slicing the data in the Swiper component before rendering, and adjusting the SwipableCard index. It works like a charm as long as I dont have to call programmatically the swipeLeft/swipeRight, nor call the swipeBack.

@Skipperlla If you have any recommandations or even a plan to finalize it, I'll be happy to create the PR and save you time.

Skipperlla commented 1 month ago

I don't have a plan for the moment, since I'm very busy, I can usually update this library every 2 or 3 months in bulk, if you open the feature you want as a pr, I can spare time to read and merge the pr. @tilap

vanenshi commented 2 weeks ago

@Skipperlla @tilap I made a fork of the library which only renders 2 next and 2 previous cards. I'll make a PR when I have time. Using this method we can also implement loop features (#37)

NeroN7F commented 3 days ago

@Skipperlla @tilap I made a fork of the library which only renders 2 next and 2 previous cards. I'll make a PR when I have time. Using this method we can also implement loop features (#37)

Any updates? Will appreciate looking at your pr

Skipperlla commented 18 hours ago

this weekend I will look at this and all other issues and pr's and close them, I apologize to all of you one by one for keeping you waiting so long @tilap @vanenshi @NeroN7F @umangloria

vanenshi commented 18 hours ago

@Skipperlla @tilap I made a fork of the library which only renders 2 next and 2 previous cards. I'll make a PR when I have time. Using this method we can also implement loop features (#37)

Any updates? Will appreciate looking at your pr

Very sorry @NeroN7F, I am a little busy right now, I am just going to share the code here.

import { fixedForwardRef } from '@/utils/ref';
import React, {
  RefObject,
  useEffect,
  useImperativeHandle,
  useMemo,
  type ForwardedRef,
} from 'react';
import {
  SwiperCardOptions,
  SwiperCardRefType,
  SwiperOptions,
  SwiperRefType,
} from '.';
import { SwipeableCard } from './SwiperCard';
import { useSwipeControls } from './hooks/useSwipeControls';

const renderSwipeable = <T,>(
  item: T | undefined,
  index: number,
  renderCard: (item: T, index: number) => JSX.Element,
  keyExtractor: ((item: T) => string) | undefined,
  ref: RefObject<SwiperCardRefType> | undefined,
  additionalProps: Omit<SwiperCardOptions, 'index'>,
) => {
  if (!item) return null;

  const key = keyExtractor ? keyExtractor(item) : index;
  return (
    <SwipeableCard key={key} ref={ref} index={index} {...additionalProps}>
      {renderCard(item, index)}
    </SwipeableCard>
  );
};

const SwiperComponent = <T,>(
  {
    data,
    onSwipedAll,
    onIndexChange,
    renderCard,
    keyExtractor,
    OverlayLabelBottom,
    OverlayLabelLeft,
    OverlayLabelRight,
    OverlayLabelTop,
    cardStyle,
    disableBottomSwipe,
    disableLeftSwipe,
    disableRightSwipe,
    disableTopSwipe,
    inputOverlayLabelBottomOpacityRange,
    inputOverlayLabelLeftOpacityRange,
    inputOverlayLabelRightOpacityRange,
    inputOverlayLabelTopOpacityRange,
    outputOverlayLabelBottomOpacityRange,
    onSwipeActive,
    onSwipeBottom,
    onSwipeEnd,
    onSwipeLeft,
    onSwipeRight,
    onSwipeStart,
    onSwipeTop,
    outputOverlayLabelLeftOpacityRange,
    outputOverlayLabelRightOpacityRange,
    outputOverlayLabelTopOpacityRange,
    rotateInputRange,
    rotateOutputRange,
    translateXRange,
    translateYRange,
  }: SwiperOptions<T>,
  ref: ForwardedRef<SwiperRefType>,
) => {
  const {
    activeIndex,
    setActiveIndex,
    refs,
    swipeRight,
    swipeLeft,
    swipeBack,
    swipeTop,
    swipeBottom,
    jumpToCard,
  } = useSwipeControls(data);

  useImperativeHandle(
    ref,
    () => ({
      swipeLeft,
      swipeRight,
      swipeBack,
      swipeTop,
      swipeBottom,
      jumpToCard,
    }),
    [swipeLeft, swipeRight, swipeBack, swipeTop, swipeBottom, jumpToCard],
  );

  useEffect(() => {
    if (activeIndex >= data.length) onSwipedAll?.();
  }, [activeIndex, data.length, onIndexChange, onSwipedAll]);

  useEffect(() => {
    onIndexChange?.(activeIndex);
  }, [activeIndex, onIndexChange]);

  const cards = useMemo(() => {
    const cardRenderingProps = {
      activeIndex,
      setActiveIndex,
      OverlayLabelBottom,
      OverlayLabelLeft,
      OverlayLabelRight,
      OverlayLabelTop,
      cardStyle,
      disableBottomSwipe,
      disableLeftSwipe,
      disableRightSwipe,
      disableTopSwipe,
      inputOverlayLabelBottomOpacityRange,
      inputOverlayLabelLeftOpacityRange,
      inputOverlayLabelRightOpacityRange,
      inputOverlayLabelTopOpacityRange,
      outputOverlayLabelBottomOpacityRange,
      onSwipeActive,
      onSwipeBottom,
      onSwipeEnd,
      onSwipeLeft,
      onSwipeRight,
      onSwipeStart,
      onSwipeTop,
      outputOverlayLabelLeftOpacityRange,
      outputOverlayLabelRightOpacityRange,
      outputOverlayLabelTopOpacityRange,
      rotateInputRange,
      rotateOutputRange,
      translateXRange,
      translateYRange,
    } satisfies Omit<SwiperCardOptions, 'index'>;

    return [
      renderSwipeable(
        data[activeIndex - 1],
        activeIndex - 1,
        renderCard,
        keyExtractor,
        refs[activeIndex - 1],
        cardRenderingProps,
      ),
      renderSwipeable(
        data[activeIndex],
        activeIndex,
        renderCard,
        keyExtractor,
        refs[activeIndex],
        cardRenderingProps,
      ),
      renderSwipeable(
        data[activeIndex + 1],
        activeIndex + 1,
        renderCard,
        keyExtractor,
        refs[activeIndex + 1],
        cardRenderingProps,
      ),
      renderSwipeable(
        data[activeIndex + 2],
        activeIndex + 2,
        renderCard,
        keyExtractor,
        refs[activeIndex + 2],
        cardRenderingProps,
      ),
    ].filter(Boolean);
  }, [
    activeIndex,
    setActiveIndex,
    OverlayLabelBottom,
    OverlayLabelLeft,
    OverlayLabelRight,
    OverlayLabelTop,
    cardStyle,
    disableBottomSwipe,
    disableLeftSwipe,
    disableRightSwipe,
    disableTopSwipe,
    inputOverlayLabelBottomOpacityRange,
    inputOverlayLabelLeftOpacityRange,
    inputOverlayLabelRightOpacityRange,
    inputOverlayLabelTopOpacityRange,
    outputOverlayLabelBottomOpacityRange,
    onSwipeActive,
    onSwipeBottom,
    onSwipeEnd,
    onSwipeLeft,
    onSwipeRight,
    onSwipeStart,
    onSwipeTop,
    outputOverlayLabelLeftOpacityRange,
    outputOverlayLabelRightOpacityRange,
    outputOverlayLabelTopOpacityRange,
    rotateInputRange,
    rotateOutputRange,
    translateXRange,
    translateYRange,
    data,
    renderCard,
    keyExtractor,
    refs,
  ]);

  return cards;
};

export const Swiper = fixedForwardRef(SwiperComponent);

it's very basic, I am sure @Skipperlla can make a better version of it 😁

a very important note: I had to change the activeIndex from shared value to a state, since in this version, sometimes the change of the activeIndex is not cause render and causes bad animation