react-native-deck-swiper
react-native-deck-swiper copied to clipboard
Cards not updating when state changes after all cards were swiped [Possible Solution]
❗️ First of all, if you are updating the cards list dynamically (e.g. adding new cards when there are 5 cards left in the current stack) and you are still facing the issue when the list doesn't update, please, make sure you have the latest version of the package. This issue should be fixed there.
Now, let's talk about our patient. Swiper doesn't update state after all cards were swiped because it considers the stack is finished and it stops adding new cards to it. Here the part of the code responsible for this:
renderStack = () => {
const { firstCardIndex, swipedAllCards } = this.state
const { cards } = this.props
const renderedCards = []
let { stackSize, infinite, showSecondCard } = this.props
let index = firstCardIndex
let firstCard = true
let cardPosition = 0
while (stackSize-- > 0 && (firstCard || showSecondCard) && !swipedAllCards) {
const key = this.getCardKey(cards[index], index)
this.pushCardToStack(renderedCards, index, cardPosition, key, firstCard)
firstCard = false
if (index === cards.length - 1) {
if (!infinite) break
index = 0
} else {
index++
}
cardPosition++
}
return renderedCards
}
swipedAllCards is a state variable, that is being set here
incrementCardIndex = onSwiped => {
const { firstCardIndex } = this.state
const { infinite } = this.props
let newCardIndex = firstCardIndex + 1
let swipedAllCards = false
this.onSwipedCallbacks(onSwiped)
const allSwipedCheck = () => newCardIndex === this.props.cards.length
if (allSwipedCheck()) {
if (!infinite) {
this.props.onSwipedAll()
// onSwipeAll may have added cards
if (allSwipedCheck()) {
swipedAllCards = true // look here
}
} else {
newCardIndex = 0;
}
}
this.setCardIndex(newCardIndex, swipedAllCards)
}
So once it is set to true
it won't ever change to false because that stack just stops rendering.
How did I manage to fix it?
❗️DISCLAIMER: I don't think this is the best solution. This is why I've opened this issue, so we could find a more universal solution.
I've moved swipedAllCards
property from state
to props
because I think we can decide ourselves when the list is finished or not. I've also added a isLoading
prop to allow managing loading state of the swiper. To prevent Swiper from stopping rendering cards inside while
case I've added CardListLoadingComponent
and CardListEmptyComponent
that are shown during the loading and empty state. Here is my renderStack
function:
renderStack = () => {
const { firstCardIndex } = this.state;
const { swipedAllCards } = this.props;
const renderedCards = [];
let {
cards,
stackSize,
infinite,
showSecondCard,
loading,
CardListLoadingComponent,
CardListEmptyComponent,
} = this.props;
let index = firstCardIndex;
let firstCard = true;
let cardPosition = 0;
if (swipedAllCards && loading) { // check for loading
return CardListLoadingComponent;
}
if (swipedAllCards && !loading) { // check for empty
return CardListEmptyComponent;
}
while (stackSize-- > 0 && (firstCard || showSecondCard)) {
const key = this.getCardKey(cards[index], index);
this.pushCardToStack(renderedCards, index, cardPosition, key, firstCard);
firstCard = false;
if (index === cards.length - 1) {
if (!infinite) break;
index = 0;
} else {
index++;
}
cardPosition++;
}
return renderedCards;
};
And here is my incrementCardIndex
function:
incrementCardIndex = (onSwiped) => {
const { firstCardIndex } = this.state;
const { infinite, swipedAllCards } = this.props;
let newCardIndex = firstCardIndex + 1;
this.onSwipedCallbacks(onSwiped);
const allSwipedCheck = () => newCardIndex === this.props.cards.length;
if (allSwipedCheck()) {
if (!infinite) {
this.props.onSwipedAll();
} else {
newCardIndex = firstCardIndex;
}
}
this.setCardIndex(newCardIndex, swipedAllCards);
};
After this refactoring I had a working Deck Swiper so I stopped looking for a more reliable and universal solution. We have a heavy backend request for the new stack of cards, so while the request is pending the loading
props is set to true
and loading component is shown. When backend stops sending new cards the swipedAllCards
is set to true
and empty component is shown.
Why not just set an integer as a key
property on the Swiper
component and just increment it whenever onSwiped
fires?
Why not just set an integer as a
key
property on theSwiper
component and just increment it wheneveronSwiped
fires?
This was the much easier fix for me!