0

The issue I am facing is when I am swiping the cards really fast then my useEffect is being called infinitely which is messing up my deck of cards. Whereas my useEffect should only be called when current card Index changes but its happening everytimes .Tried a lot not able to figure out "I am noob in react/react-native". I m pasting code for my code, please help me out, any help will be appreciated

import React, {useState, useEffect} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import styled from 'styled-components';
import Swiper from 'react-native-deck-swiper';
import ReactionCTAs from '../reactionCTAs/ReactionCTAs';
import MovieDetails from '../cardMovieDetails/MovieDetails';

const Recommendations = () => {
  const [currentCardIndex, setCount] = useState(0);
  const [loading, setLoading] = useState(true);
  const [cardsState, updateState] = useState({
    cards: [],
    swipedAllCards: false,
    swipeDirection: '',
    cardIndex: 0,
    pageNumber: 1,
  });

  useEffect(() => {
    console.log('Use effect being called');

    fetch(
      `https://abcde`,
      {
        method: 'GET',
        headers: {
          Accept: 'application/json',
          Authorization: 'Token ',
        },
      },
    )
      .then((response) => response.json())
      .then((json) => {
        setLoading(false);
        updateState({
          ...cardsState,
          cards: [...cardsState.cards, ...json.data],
        });
      })
      .catch((error) => {
        console.error(error);
      });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentCardIndex]);

  const renderCard = (card, index) => {
    return (
      <Card>
        {cardsState.cards[index] !== undefined ? (
          <CardImage
            source={{
              uri: cardsState.cards[index].media.posters[0],
            }}>
            <MovieDetails movie={cardsState.cards[index]} />
            <ReactionCTAs
              movieId={cardsState.cards[index].id}
              swipeLeft={swipeLeft}
              swipeRight={swipeRight}
              swipeTop={swipeTop}
              swipeBottom={swipeBottom}
            />
          </CardImage>
        ) : (
          <Text>Undefined</Text>
        )}
      </Card>
    );
  };

  const onSwiped = (type) => {
    setCount((prevCurrentCardIndex) => prevCurrentCardIndex + 1);
    updateState((prevState) => ({
      ...prevState,
      pageNumber: prevState.pageNumber + 1,
    }));
    if (type === 'right') {
      fetch('https://abcde', {
        method: 'POST',
        body: `{"movie":${cardsState.cards[currentCardIndex].id}}`,
        headers: {
          'Content-type': 'application/json',
          Authorization: 'Token',
        },
      });
    }
  };

  const onSwipedAllCards = () => {
    updateState({
      ...cardsState,
      swipedAllCards: true,
    });
  };

  const swipeLeft = () => {
    cardsState.swiper.swipeLeft();
  };

  const swipeRight = () => {
    cardsState.swiper.swipeRight();
  };
  const swipeTop = () => {
    cardsState.swiper.swipeTop();
  };
  const swipeBottom = () => {
    cardsState.swiper.swipeBottom();
  };

  return loading ? (
    <ProgressBar animating={true} color="red" size="large" />
  ) : (
    <Container>
      <Swiper
        ref={(swiper) => {
          cardsState.swiper = swiper;
        }}
        backgroundColor={'#20242b'}
        onSwiped={() => onSwiped('general')}
        onSwipedLeft={() => onSwiped('left')}
        onSwipedRight={() => onSwiped('right')}
        onSwipedTop={() => onSwiped('top')}
        onSwipedBottom={() => onSwiped('bottom')}
        onTapCard={swipeLeft}
        cards={cardsState.cards}
        cardIndex={cardsState.cardIndex}
        cardVerticalMargin={80}
        renderCard={renderCard}
        children={true}
        stackScale={4.5}
        onSwipedAll={onSwipedAllCards}
        stackSize={3}
        stackSeparation={-25}
        overlayLabels={{
          bottom: {
            title: 'BLEAH',
            style: {
              label: {
                backgroundColor: 'black',
                borderColor: 'black',
                color: 'white',
                borderWidth: 1,
              },
              wrapper: {
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
              },
            },
          },
          left: {
            title: 'NOPE',
            style: {
              label: {
                backgroundColor: 'black',
                borderColor: 'black',
                color: 'white',
                borderWidth: 1,
              },
              wrapper: {
                flexDirection: 'column',
                alignItems: 'flex-end',
                justifyContent: 'flex-start',
                marginTop: 30,
                marginLeft: -30,
              },
            },
          },
          right: {
            title: 'LIKE',
            style: {
              label: {
                backgroundColor: 'black',
                borderColor: 'black',
                color: 'white',
                borderWidth: 1,
              },
              wrapper: {
                flexDirection: 'column',
                alignItems: 'flex-start',
                justifyContent: 'flex-start',
                marginTop: 30,
                marginLeft: 30,
              },
            },
          },
          top: {
            title: 'SUPER LIKE',
            style: {
              label: {
                backgroundColor: 'black',
                borderColor: 'black',
                color: 'white',
                borderWidth: 1,
              },
              wrapper: {
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
              },
            },
          },
        }}
        animateOverlayLabelsOpacity
        animateCardOpacity
        swipeBackCard
      />
    </Container>
  );
};

const Container = styled.View`
  flex: 1;
`;

const CardImage = styled.ImageBackground`
  height: 100%;
  width: 372px;
`;

const Card = styled.View`
  flex: 1;
  border-radius: 4px;
  justify-content: center;
  background-color: #20242b;
`;

const ProgressBar = styled.ActivityIndicator`
  flex: 1;
  justify-content: center;
  align-items: center;
`;

export default Recommendations;

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
sarthak gupta
  • 826
  • 4
  • 12

1 Answers1

1

It is unclear what your code is supposed to do but fetching data based on user action has some pitfalls.

  1. Debounce fetching so fetch is only triggered when user is inactive for a certain amount of time.
  2. Only resolve when it was the last user action, why you should do this is explained here

So here are some helper functions you can use:

//helper to make sure promise only resolves when
//  it was the last user action
const last = (fn) => {
  const check = {};
  return (...args) => {
    const last = {};
    check.last = last;
    return Promise.resolve(fn(...args)).then((resolve) =>
      check.last === last
        ? resolve
        : Promise.reject('replaced by newer request')
    );
  };
};
const debounce = (fn, time = 300) => {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), time);
  };
};
// only resolve if it was the last user action
const lastFetch = debounce(last(fetch));
// the debounced effect (only run when inactive for 300ms)
const effect = debounce(
  (pageNumber, setLoading, updateState) => {
    //only resolve when it was the last user action
    lastFetch(`https://abcde/?page=${pageNumber}`, {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        Authorization: 'Token',
      },
    })
      .then((response) => response.json())
      .then((json) => {
        setLoading(false);
        //pass callback to upateState so cardsState is not
        //  a dependency of the effect
        updateState((cardsState) => ({
          ...cardsState,
          cardIndex: 0,
          cards: [...cardsState.cards, ...json.data],
        }));
      })
      .catch((error) => {
        console.error(error);
      });
  }
);

And here is how you can run the effect:

useEffect(() => {
  //debounced and resolve last
  effect(cardsState.pageNumber, setLoading, updateState);
  //if you don't add pageNumber then pageNumber will be
  //  a stale closure and request with new pageNumber
  //  will never be made
}, [cardsState.pageNumber, cardsState.currentCardIndex]);
HMR
  • 37,593
  • 24
  • 91
  • 160
  • Currently what i m doing is I have made a deck of cards and on swiping one card I am trying to add new cards to deck. The issue now I am facing is that whenever I swipe cards really really fast then my ''useEffect'' is being called twice which is messing up my deck. Please let me know whats wrong in my code that my useEffect is being messed up. Ps - I looked at your code and wasnt able to understand much because I am a noob and I think its my 3rd or 4th day with both react/react-native and javascript – sarthak gupta Jul 17 '20 at 11:29