0

I'm not used to using ref, so i have a question. I'm using react-native-deck-swiper to display some cards. People are able to vote by swiping the cards. To be as clear as possible :

  • My swiper is displayed as an item with other items from multiple types in my flatlist
  • My items are rendered conditionally by types of items, if my item is a swiper type, im displaying a swiper, if it is a simple text, i'm displaying a text etc...
  • So I potentially have multiple swipers in my flatlist

Here is the json to display my swipers :

{
            "id": 577,
            "type": "copernic",
            "entity": {
                "id": 715,
                "str_id": "rCckubRTIDz2D3N5wcKW",
                "display_name": "Force Républicaine",
                "name": "Force Républicaine",
                "title": "Dépense publique",
                "description": null,
                "type": 2,
                "str_type": "Pour / Contre / Neutre",
                "is_opened": 1,
                "str_is_opened": "Les utilisteurs peuvent soumettre des propositions",
                "order_propositions_by": 3,
                "display_votes": 1,
                "login_to_vote": 0,
                "str_login_to_vote": "Les utilisateurs ne doivent pas se connecter pour voter",
                "login_to_send_proposition": 1,
                "str_login_to_send_proposition": "Les utilisateurs doivent se connecter pour envoyer une proposition",
                "begins_at": "2021-03-02T17:45:55.000000Z",
                "ends_at": null,
                "propositions": [
                    {
                        "id": 6572,
                        "title": "Pensez-vous qu'il faille annuler la dette COVID ?",
                        "description": "",
                        "auteur": "Force Républicaine",
                        "type": 1,
                        "media_link": null,
                        "video_frame": null
                    },
                    {
                        "id": 6571,
                        "title": "Trouvez-vous la qualité des services publics en France à la hauteur du niveau de la dépense publique du pays ?",
                        "description": "",
                        "auteur": "Force Républicaine",
                        "type": 1,
                        "media_link": null,
                        "video_frame": null
                    }
                ]
            },
            "is_pinned": 0,
            "created_at": "2021-03-18T13:27:03.000000Z"
        },

So each swipers could have as a ref, the item.entity.id or the item.entity.id.

My swipers are displaying the item.entity.propositions as cards. Each card have thumbs to vote right, up or left. And I need a ref to do something as swiperRef.current.swipeRight() when we tap on a thumb or to tell the user that every cards from this swiper have been swiped by displaying a message. And now the message is displaying on all swipers and that't not what I want :)

Here's the code for my swiper :

if (
      item.type === "copernic" &&
      item.entity.str_type === "Pour / Contre / Neutre"
    ) {
       return (
         <View style={styles.swipperWrapper} key={item.id}>
          {isSwipedAll ? (
            <View style={styles.emptyCopernic}>
              <View style={styles.emptyCopernicColor} />
              <View style={styles.copContainer}>
                <Text style={styles.copTitle}>
                  You have swiped all the cards! Thanx for your participation !
                </Text>
              </View>
            </View>
          ) : (
            <Swiper
              ref={useSwiper}
              //animateCardOpacity
              containerStyle={styles.swiperContainer}
              cards={item.entity.propositions}
              renderCard={(card, index) => (
                <CardCopernic
                  {...index}
                  copKey={card.id}
                  copTitle={card.title}
                  copDesc={card.description}
                  strid={strid}
                  onPressNo={() => swipeLeft(card.id, index)}
                  onPressMaybe={() => swipeTop(card.id, index)}
                  onPressYes={() => swipeRight(card.id, index)}
                />
              )}
              onSwipedLeft={(card) => handleNo(card)}
              onSwipedTop={(card) => handleMaybe(card)}
              onSwipedRight={(card) => handleYes(card)}
              onSwipedAll={onSwipedAllCards}
              cardIndex={cardIndex}
              stackSize={3}
              stackScale={5}
              showSecondCard
              backgroundColor="white"
              cardVerticalMargin={2}
              disableBottomSwipe
              overlayLabels={{
                left: {
                  title: "Contre",
                  style: {
                    label: {
                      borderColor: "#FF8585",
                      color: "#FF8585",
                      borderWidth: 2,
                    },
                    wrapper: {
                      flexDirection: "column",
                      alignItems: "flex-end",
                      justifyContent: "flex-start",
                      marginTop: 30,
                      marginLeft: -30,
                    },
                  },
                },
                right: {
                  title: "Pour",
                  style: {
                    label: {
                      borderColor: "#FF8585",
                      color: "#FF8585",
                      borderWidth: 2,
                    },
                    wrapper: {
                      flexDirection: "column",
                      alignItems: "flex-start",
                      justifyContent: "flex-start",
                      marginTop: 30,
                      marginLeft: 30,
                    },
                  },
                },
                top: {
                  title: "Neutre",
                  style: {
                    label: {
                      borderColor: "#FF8585",
                      color: "#FF8585",
                      borderWidth: 2,
                    },
                    wrapper: {
                      flexDirection: "column",
                      alignItems: "center",
                      justifyContent: "center",
                    },
                  },
                },
              }}
            />
          )}
        </View>
      );

Here's one of the function to swipe the card to the left, right or top :

swipeLeft = async (cardId, index) => {
        console.log("CARDINDEX : ", index);

        const cardKey = cardId;
        const strid = item.entity.str_id;
        const token = await AsyncStorage.getItem("token");
        useSwiper.current[strid].swipeLeft(cardId);

        const requestOptions = {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "X-Requested-With": "XMLHttpRequest",
            Authorization: `Bearer ${token}`,
          },
          body: JSON.stringify({
            vote: "contre",
            proposition_id: `${cardKey}`,
            consultation_str_id: `${strid}`,
          }),
        };

        try {
          let response = await fetch(
            `${c.API_URL_DEV}/copernic/vote/add`,
            requestOptions
          );
          let json = await response.json();
          if (index === item.entity.propositions.length - 1) {
            setSwipedAll(true);
            await AsyncStorage.setItem("hasAllSwiped", JSON.stringify(true));
          }
          return json;
        } catch (error) {
          console.error(error);
        }
      };

Any help is appreciated ! I'm a little bit stuck here and not too familiar with refs haha x And my items are functional components :)

If you need more informations to help, let me know ! :)

  • It's a bit hard to tell what the scope of a piece of state like `isSwipedAll` is - it looks like you may only one such piece of state for your whole app when you'll need one for each swiper. I would think about what state should be replicated for each Swiper and wrap the Swiper in another component to encapsulate that state. Managing dynamic refs is a bit trickier, check out this question for some suggestions: https://stackoverflow.com/q/55995760/11847125 – azundo Mar 18 '21 at 17:40
  • @azundo I understand what you mean. IsSwipedAll is not what I really want... I'm just setting to true the fact that all my cards are swiped when the user reach the last card of the swiper but it's not really targeting "this" swiper. I already read a lot of articles about dynamic refs etc and I can't wrap my head around it... I'll check out your link, thanks for taking the time to respond :) – Prisca Aure Mar 18 '21 at 17:49
  • I think you may be able to skip the dynamic refs if you encapsulate everything in the second code block into a component with its own state for `isSwipedAll` and its own ref. I'll try to write up a quick example. – azundo Mar 18 '21 at 18:08

1 Answers1

0

Try encapsulating the related swiper code into a component. This will remove the need for dynamic refs and properly encapsulate state.

Something like:

function MySwiper({item}) {
  const swiperRef = useRef(null);
  const [isSwipedAll, setSwipedAll] = useState(false);
  const swipeLeft = useCallback(async (cardId, index) => {
    console.log("CARDINDEX : ", index);

    const cardKey = cardId;
    const strid = item.entity.str_id;
    const token = await AsyncStorage.getItem("token");
    swiperRef.current.swipeLeft(cardId);

    const requestOptions = {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Requested-With": "XMLHttpRequest",
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify({
        vote: "contre",
        proposition_id: `${cardKey}`,
        consultation_str_id: `${strid}`,
      }),
    };

    try {
      let response = await fetch(
        `${c.API_URL_DEV}/copernic/vote/add`,
        requestOptions
      );
      let json = await response.json();
      if (index === item.entity.propositions.length - 1) {
        setSwipedAll(true);
        await AsyncStorage.setItem("hasAllSwiped", JSON.stringify(true)); // TODO: index this somehow by the item
      }
      return json;
    } catch (error) {
      console.error(error);
    }
  }, [item]);

  return (
    <View style={styles.swipperWrapper} key={item.id}>
      {isSwipedAll ? (
        <View style={styles.emptyCopernic}>
          <View style={styles.emptyCopernicColor} />
          <View style={styles.copContainer}>
            <Text style={styles.copTitle}>
              You have swiped all the cards! Thanx for your participation !
            </Text>
          </View>
        </View>
      ) : (
        <Swiper
          ref={useSwiper}
          //animateCardOpacity
          containerStyle={styles.swiperContainer}
          cards={item.entity.propositions}
          renderCard={(card, index) => (
            <CardCopernic
              {...index}
              copKey={card.id}
              copTitle={card.title}
              copDesc={card.description}
              strid={strid}
              onPressNo={swipeLeft}
              onPressMaybe={() => swipeTop(card.id, index)} // UPDATE
              onPressYes={() => swipeRight(card.id, index)} // UPDATE
            />
          )}
          onSwipedLeft={(card) => handleNo(card)} // UPDATE
          onSwipedTop={(card) => handleMaybe(card)} // UPDATE
          onSwipedRight={(card) => handleYes(card)} // UPDATE
          onSwipedAll={onSwipedAllCards}
          cardIndex={cardIndex}
          stackSize={3}
          stackScale={5}
          showSecondCard
          backgroundColor="white"
          cardVerticalMargin={2}
          disableBottomSwipe
          overlayLabels={...}
        />
      )}
    </View>
  );
}

You'll need to create the other callbacks and fix they storing of state in AsyncStorage to be keyed based on each item.

azundo
  • 5,902
  • 1
  • 14
  • 21
  • Thank you so much azundo ! I understand the logic behind this, way more suitable than refs :) I'll give it a try x – Prisca Aure Mar 18 '21 at 18:28