Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Cantum2 opened this issue Mar 1, 2021 · 11 comments

Comments

@Cantum2
Copy link

Cantum2 commented Mar 1, 2021

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...

20210228-184243-720x1440.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.

@mirza-osv
Copy link

Can you try to update the value of cardIndex ( index) in the parent component as shown here https://github.com/alexbrillant/react-native-deck-swiper#updating-props-on-card-content-dynamic-card-content

@Cantum2
Copy link
Author

Cantum2 commented Mar 3, 2021

I have done this and 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}
...

@felire
Copy link

felire commented Mar 5, 2021

I am having exactly the same issue

@Cantum2
Copy link
Author

Cantum2 commented Mar 7, 2021

I believe many people have had this issue. Its a massive bummer that they will not fix it. I still dont have a solution...

@kitmade
Copy link

kitmade commented Mar 15, 2021

hey guys, i'm facing the same issue here, we've tried push method to initial list in reducer or use the spread operator, but still not be able to show the data when we're receiving from server (but it only happens on the 1st time get data from server, all time after that Swiper works just fine).

@felire
Copy link

felire commented Mar 15, 2021

Okey, I could make it work but with a lot of hacks: It is something like this:

This is how I implement the CustomSwiper, which implement the swiper:

              <CustomSwiper
                pets={[...candidates, { image: noMatchPets }]}
                onSwipedRight={onSwipe('like')}
                onSwipedLeft={onSwipe('dislike')}
                initialIndex={swipeIndex.current + 1}
                key={candidates.length}
                onTapCard={onTapCard}
                ref={swiper}
                isLastDataSet={isLastDataSet}
                offset={OFFSET}
                type={'match'}
              />

I am updating the key whenever the length of the candidates change so it will re-render, and since that is rerendering I can send it a reference and it will take the value of that reference to set the initial-value, since the list of candidates already has the old values but I am like ignoring them on the re-render because they were already swiped.

I mean, I had to make re-renders constantly whenever the data is modified.

I am re-rendering when the user unfocus the screen too.

@Cantum2
Copy link
Author

Cantum2 commented Mar 17, 2021

The solution mentioned by @felire worked! Simply adding the length of the array as the key re-rendered my list with my paginated data. Here is what my code looks like that contains this:

...
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}
            cardHorizontalMargin={5}
            stackSize={stackSize}

            key={products.length} // HERE IS THE SOLUTION

            stackScale={3}
            stackSeparation={2}
            animateOverlayLabelsOpacity
            animateCardOpacity
            showSecondCard={true}
            disableTopSwipe
            disableBottomSwipe
...

I do believe this solution is a bit of a hack as we have to rerender the entire list every single swipe. I would like to leave this issue open as this issue has happened for dozens of developers and it would be great if the maintainers implemented a more elegant solution for this.

@farazirfan47
Copy link

By simply adding the componentDidUpdate method in Swiper.js we can dynamically update cards without any lag or something.

  componentDidUpdate(prevProps){
    if(prevProps.cards.length != this.props.cards.length){
      this.setState({
        cards: this.props.cards
      })
    }
  }

@salemshah
Copy link

salemshah commented Aug 24, 2022

"👁️👁️SIMPLE"
I had the same issue, but I solved it with @felire 's help

const [cards, setCards] = useState(['DO', 'MORE', 'OF', 'WHAT', 'MAKES', 'YOU', 'HAPPY'])  //main 

const updateCards = () => {
    setCards(["jan", "khan", "karim"]) // These cards appear after all main cards have been swiped 👈
}

return (
        <Swiper
            cards={cards}
            renderCard={(card) =><Card title={card} />}

            key={cards.length}  // important to add 👈👈👈

            onSwipedAll={updateCards}/>
)

@bsmithcompsci
Copy link

const HomeView = () => {
   // Some hooks to do our madness.
    const [cards, setCards] = useState<Profile[]>(data);
    const [cardLength, setCardLength] = useState<number>(data.length);

    var cardStack : Swiper<Profile>|undefined = undefined; // Store off a reference of the swiper.

    const updateCards = () => {
        setCards(data); // Update with the new cards that we want, but it won't be rendered yet.
        setCardLength(0); // Lie to the UI that we have nothing in our cards!
        cardStack.forceUpdate(()=>{
            setCardLength(data.length); // Now tell the truth, we have a new length of cards.
        });
    }

    return (
        <Swiper<Profile>
            cards={cards}
            key={cardLength}
            renderCard={(item) => {
                return (
                  <MyCardComponent
                    image={item.image}
                    name={item.name}
                    onPressLeft={() => cardStack.swipeLeft()}
                    onPressRight={() => cardStack.swipeRight()}
                  />
                )
            }}
            ref={(ref)=>{ cardStack = ref; }}
            onSwipedAll={updateCards}
            backgroundColor={'#4FD0E9'}
            stackSize= {3}
            animateOverlayLabelsOpacity
            animateCardOpacity
            swipeBackCard
        />
    );
};

This was my solve. You have to play a little bit with the key, apparently that triggers the actual "re-render" of the Swiper. Also make sure you are making this a component, otherwise you cannot use the hooks. And this was written for Typescript, so if you cannot have the strong types, remove them and you got Javascript. Happy coding! 😄

@1finedev
Copy link

Thank you very much @bsmithcompsci

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants