react-native-deck-swiper icon indicating copy to clipboard operation
react-native-deck-swiper copied to clipboard

Issues showing new cards after paginated data is returned and added to the card props list

Open Cantum2 opened this issue 4 years ago • 9 comments

Cross Posted

This issue is cross posted to here as I was not sure which repository is being actively maintained.

Issue

When dynamically adding elements to the Swiper card prop, the newly added properties will not appear on the screen.

Longer Description

My issue comes from the following situation: My data is paginated, so I get the first page of data initially which consists of 5 elements which loads just fine and I am able to swipe on them no problem. I begin to load page two when the user is 2 away from the end of the five, once the data is returned I concat the array returned with page 2 data on to the array of page 1 data. This is where the issue is: The first five from page 1 show perfectly fine and I am able to interact with them flawlessly but when I try to concat page two to the existing array passed into the props of the Swiper the new items dont show up.

Expectation

I would expect when I modify the array being passed into the cards prop that change is reflected in the swiper and I can interact with the newly added array elements

Video

Ignore Ugly UI, its under construction...

https://user-images.githubusercontent.com/26589485/109442265-f091cc00-79f4-11eb-8eaf-132eb5552208.mp4

As you can see in the video the total property correctly updates to 10 after the second page of data is loaded in but once I reach the end of the first pages data the swiper fails to show the next 5 elements (the second page)

Related issues I have visited numerous times

#153, This Comment, #192, #189, and This Section in the Readme

Code

Swiper

  const [index, setIndex] = useState(0);
  const [products, setProducts] = useState<IProduct[]>([]);
...
const getProductsByTag = () => {
    axios.get('/products/list', {
      params: { tags: tags.join(','), page, size: 5 }
    }).then((res) => {
      const { products: internalProds, count } = res.data;
      setTotalSize(count);
      setProducts(products.concat(internalProds));
    }).catch((err) => {
      setError('Error getting products');
    })
  }
...
  const userLikeProduct = (product: IProduct) => {
    setIndex((index + 1) % products.length);
    transitionRef.current.animateNextTransition();

   if(products.length - 2 <= index) {
        console.log('getting more products in like product')
        setPage(page++)
        getProductsByTag();
      }
  }
...
  const renderCard = (tProduct: IProduct, tIndex: number) => {
    return <Card key={tProduct._id} index={tIndex} card={tProduct} />
  }
...
  useDidMountEffect(() => {
    getProductsByTag();
  }, [tags, page])
...
return (
....
{products.length ?
...
 <Swiper ref={swiperRef}
              cards={products}
              cardIndex={index}
              renderCard={(card, cardIndex) => {return renderCard(card, cardIndex)}}
              onSwipedRight={(num) => userLikeProduct(products[index])}
              onSwipedLeft={(num) => userDislikeProduct(products[index])}
              backgroundColor={'transparent'}
              onTapCard={() => userDislikeProduct(products[index])}
              cardVerticalMargin={0}
              cardHorizontalMargin={5}
              stackSize={stackSize}
              stackScale={3}
              stackSeparation={2}
              animateOverlayLabelsOpacity
              animateCardOpacity
              showSecondCard={true}
              disableTopSwipe
              disableBottomSwipe
              overlayLabels={{
                left: {
                  title: 'NOPE',
                  style: {
                    label: {
                      backgroundColor: colors.red,
                      borderColor: colors.red,
                      color: colors.white,
                      borderWidth: 1,
                      fontSize: 24
                    },
                    wrapper: {
                      flexDirection: 'column',
                      alignItems: 'flex-end',
                      justifyContent: 'flex-start',
                      marginTop: 20,
                      marginLeft: -20
                    }
                  }
                },
                right: {
                  title: 'LIKE',
                  style: {
                    label: {
                      backgroundColor: colors.blue,
                      borderColor: colors.blue,
                      color: colors.white,
                      borderWidth: 1,
                      fontSize: 24
                    },
                    wrapper: {
                      flexDirection: 'column',
                      alignItems: 'flex-start',
                      justifyContent: 'flex-start',
                      marginTop: 20,
                      marginLeft: 20
                    }
                  }
                }
              }}
            />
...
 :
        <View>
          <Text>Loading</Text>
        </View>
)

Package.json

{
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "eject": "expo eject",
    "test": "jest --watchAll"
  },
  "jest": {
    "preset": "jest-expo"
  },
  "dependencies": {
    "@cantum2/simply-lib": "^1.0.7",
    "@expo/react-native-action-sheet": "^3.8.0",
    "@expo/vector-icons": "^12.0.0",
    "@react-native-community/async-storage": "^1.12.1",
    "@react-native-community/masked-view": "0.1.10",
    "@react-navigation/bottom-tabs": "5.11.2",
    "@react-navigation/material-top-tabs": "^5.3.13",
    "@react-navigation/native": "~5.8.10",
    "@react-navigation/stack": "~5.12.8",
    "axios": "^0.21.1",
    "expo": "~40.0.0",
    "expo-asset": "~8.2.1",
    "expo-constants": "~9.3.0",
    "expo-font": "~8.4.0",
    "expo-linking": "~2.0.0",
    "expo-splash-screen": "~0.8.0",
    "expo-status-bar": "~1.0.3",
    "expo-web-browser": "~8.6.0",
    "react": "16.13.1",
    "react-dom": "16.13.1",
    "react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz",
    "react-native-deck-swiper": "^2.0.5",
    "react-native-gesture-handler": "~1.8.0",
    "react-native-reanimated": "~1.13.0",
    "react-native-safe-area-context": "3.1.9",
    "react-native-screens": "~2.15.2",
    "react-native-tab-view": "^2.15.2",
    "react-native-web": "~0.13.12"
  },
  "devDependencies": {
    "@babel/core": "~7.9.0",
    "@types/react": "~16.9.35",
    "@types/react-native": "~0.63.2",
    "jest-expo": "~40.0.0",
    "typescript": "~4.0.0"
  },
  "private": true
}

If there is anything else you would like to see from me please feel free to let me know. Thank you.

Cantum2 avatar Mar 01 '21 02:03 Cantum2

@Cantum2 thanks for the detailed report! There's a couple of open PRs which I hope I'll get the time to review & merge this week. I don't think any of them fixes this issue though

webraptor avatar Mar 01 '21 06:03 webraptor

No problem! I know the struggle when people arent thorough.

Is there a temporary work around I could implement or something of the sort for this to work? This is a crucial feature for my application.

Cantum2 avatar Mar 01 '21 16:03 Cantum2

For us what worked was to keep the cardIndex outside of the app, so whenever we pushed new cards to the deck that would get updated and the deck would re-render

webraptor avatar Mar 01 '21 16:03 webraptor

Are you suggesting creating a wrapper for my swiper functional component that contains the index. Then I pass the index from the wrapper into the swiper component?

I'm a bit new to react native so I hope I'm making sense.

Cantum2 avatar Mar 01 '21 16:03 Cantum2

Are you suggesting creating a wrapper for my swiper functional component that contains the index. Then I pass the index from the wrapper into the swiper component?

I'm a bit new to react native so I hope I'm making sense.

Yes, I think the workaround was mentioned before in at least one of the related issues you've mentioned.

webraptor avatar Mar 01 '21 16:03 webraptor

I had seen that solution be suggested in there but misunderstood it. I will give this a try and let you know how it works and if it does I'll post the code because I've seen many people run into this same issue.

Thank you for being so prompt!

Cantum2 avatar Mar 01 '21 16:03 Cantum2

I moved the index to the parent and pass it into the child and still no luck, the renderCard function in the Swiper does not get triggered thus resulting in the new items not rendering. I am still stuck on this, the behavior is exactly the same. Please let me know if you see something wrong here.

Parent

...
  const getProductsByTag = () => {
    console.log('fetching products', tags);
    setLoading(true);
    axios.get('/products/list', {
      params: { tags: tags.join(','), page, size: 5 }
    }).then((res) => {
      const { products: internalProds, count } = res.data;
      const productResponse: IProduct[] = internalProds;
      console.log(count);
      setLoading(false);

      setTotalSize(count);
      setProducts(products.concat(productResponse));
    }).catch((err) => {
      setError('Error getting products');
    })
  }
...
  const userLikeProduct = (product: IProduct) => {
    setIndex((index + 1) % products.length);
    transitionRef.current.animateNextTransition();

    const likeActions = [
// calls removed for brevity
    ]

    Promise.all(likeActions).then((res) => {
      if (products.length - 2 <= index) {
        console.log('getting more products in like product')
        setPage(page++)
        getProductsByTag();
      }
    }).catch((err) => {
      console.log("Error in user like product")
    })
  }
...
<View style={{ height: 25 }} />
          <View style={SwipeStyles.swiperContainer}>
            <SimplySwiper index={index} onUserLike={userLikeProduct} onUserDislike={userDislikeProduct} products={products}/>
</View>
...

Child

... 
interface props {
    index: number;
    onUserLike: (product: IProduct) => void;
    onUserDislike: (product: IProduct) => void;
    products: IProduct[];
}
...
const renderCard = (tProduct: IProduct, tIndex: number) => {
    console.log('Render card', tProduct._id, tIndex)
    return <ProductCard key={tProduct._id} index={tIndex} card={tProduct} />
}
...
export const SimplySwiper: FC<props> = ({ index, onUserDislike, onUserLike, products }) => {
    return (
        <Swiper ref={swiperRef}
            cards={products}
            cardIndex={index}
            renderCard={(card, cardIndex) => renderCard(card, cardIndex)}
            onSwipedRight={(num) => onUserLike(products[index])}
            onSwipedLeft={(num) => onUserDislike(products[index])}
            backgroundColor={'transparent'}
            onTapCard={() => onUserDislike(products[index])}
            cardVerticalMargin={0}
...

Cantum2 avatar Mar 03 '21 01:03 Cantum2

I know the fact that causes this issue. And i have solution for the same. Props are getting changed but state value of swiper in state in cards is not changing so i set below code in shouldComponentUpdate method. Please make sure you update that and check that again.

if (propsChanged || stateChanged)
      this.setState({ cards: props.cards })

Satishpethani92 avatar May 19 '21 08:05 Satishpethani92

I posted the solution that worked for me in the crossposted issue found here: https://github.com/alexbrillant/react-native-deck-swiper/issues/346#issuecomment-800725507

Cantum2 avatar May 22 '21 18:05 Cantum2