Home > Net >  Preventing Pressable activation when dragging FlatList in React Native
Preventing Pressable activation when dragging FlatList in React Native

Time:07-23

I am using a a carousel library (that uses a FlatList) to drag things in React Native.

I've got this for the carousel:

import * as React from "react";
import Carousel from "react-native-reanimated-carousel";
import Card from "components/cards/Card";

function CardCarousel(props) {
  return (
    <Carousel
      scrollAnimationDuration={200}
      width={300}
      windowSize={9}
      moveSize={5}
      style={{
        width: 2000,
        height: 300,
      }}
      data={props.articleGroup.data}
      renderItem={(article, index) => (
        <Card
          article={article.item}
          key={index   "-main-"   props.articleGroup.category}
        />
      )}
    />
  );
}

export default CardCarousel;

And then my card looks like this:

const Card = (props) => {
  const [bookmark, setBookmark] = useState(props.article.bookmark);
  const cdnURL = wrapImgCDN(props.article.image, 400);

  const routeChange = async (event) => {
    ... code to change route
  };

  const _bookmarkToggle = () => {
    bookmarkToggle(props.article);
    setBookmark(!bookmark);
  };

  return (
    <Pressable
      onPress={routeChange}
    >
      <WhiteCard
        {...props}
        _bookmarkToggle={_bookmarkToggle}
        cdnURL={cdnURL}
        bookmark={bookmark}
      />
    </Pressable>
  );
};

When I am dragging the FlatList, if I do so by grabbing onto one of the <Card /> items, it clicks and changes the route to the given card, but I don't want to allow a user to click into a card unless the scrolling is stopped/the user didn't drag at all.

How can I do that?

CodePudding user response:

Since you are wrapping the rendered items of the carousel into a Pressable and fire the routeChange in its onPress function, it will change the route as soon as you press one of the items in the carousel.

We could use the onSnapToItem callback function of the react-native-reanimated-carousel component instead. This function is passed as a prop to the component and will be called as soon as an item in the FlatList 'snaps'. It receives the index of the item that will be snapped. We can compare this with the current item and whenever it changes, we pass a boolean prop to the Card component.

The current index can be accessed using ref via the getCurrentIndex function as usual.

Notice that this needs an additional state. However, we only change the state if the item has actually changed, that is, a new item snapped into position.

The boolean state will be true for the current snapped item, and false for the other ones. We react on fireRouteChange prop changes in a useEffect in the Card component.

function CardCarousel(props) {
  const ref = React.useRef(null);
  const [currentIndex, setCurrentIndex] = React.useState(0);

  return (
    <Carousel
      ref={ref}
      onSnapToItem={(index) => index !== ref.current.getCurrentIndex() && setCurrentIndex(index)}
      scrollAnimationDuration={200}
      width={300}
      windowSize={9}
      moveSize={5}
      style={{
        width: 2000,
        height: 300,
      }}
      data={props.articleGroup.data}
      renderItem={(article, index) => (
        <Card
          article={article.item}
          fireRouteChange={currentIndex === index}
          key={index   "-main-"   props.articleGroup.category}
        />
      )}
    />
  );
}

const Card = (props) => {
  const [bookmark, setBookmark] = useState(props.article.bookmark);
  const cdnURL = wrapImgCDN(props.article.image, 400);

  React.useEffect(() => {
    if(props.fireRouteChange) {
       ... code to change route
    }
  }, [props.fireRouteChange])

  const _bookmarkToggle = () => {
    bookmarkToggle(props.article);
    setBookmark(!bookmark);
  };

  return (
      <WhiteCard
        {...props}
        _bookmarkToggle={_bookmarkToggle}
        cdnURL={cdnURL}
        bookmark={bookmark}
      />
  );
};

Assumption: The routeChange function can be located in CardCarousel

We can make this simpler if we assume that the routeChange function can be located in CardCarousel. I do not have access to the implementation details of this function, thus I include this here by making this assumption.

function CardCarousel(props) {
  const ref = React.useRef(null);

 function routeChange(index) {

    ... code to change route of the current snapped index
 };

  return (
    <Carousel
      ref={ref}
      onSnapToItem={(index) => index !== ref.current.getCurrentIndex() && fireRouteChange(index)}
      scrollAnimationDuration={200}
      width={300}
      windowSize={9}
      moveSize={5}
      style={{
        width: 2000,
        height: 300,
      }}
      data={props.articleGroup.data}
      renderItem={(article, index) => (
        <Card
          article={article.item}
          fireRouteChange={currentIndex === index}
          key={index   "-main-"   props.articleGroup.category}
        />
      )}
    />
  );
}

const Card = (props) => {
  const [bookmark, setBookmark] = useState(props.article.bookmark);
  const cdnURL = wrapImgCDN(props.article.image, 400);

  const _bookmarkToggle = () => {
    bookmarkToggle(props.article);
    setBookmark(!bookmark);
  };

  return (
      <WhiteCard
        {...props}
        _bookmarkToggle={_bookmarkToggle}
        cdnURL={cdnURL}
        bookmark={bookmark}
      />
  );
};

Disable the onPress function while the user is scrolling

If you want to disable the onPress function while the user is scrolling and only allow it while the carousel is snapped in place, you can use a state and the onScrollBegin and onScollEnd functions.

function CardCarousel(props) {

  const [allowRouteChange, setAllowRouteChange] = useState(true) 

  return (
    <Carousel
      scrollAnimationDuration={200}
      width={300}
      windowSize={9}
      moveSize={5}
      onScrollBegin={() => setAllowRouteChange(false)}
      onScrollEnd={() => setAllowRouteChange(true)}
      style={{
        width: 2000,
        height: 300,
      }}
      data={props.articleGroup.data}
      renderItem={(article, index) => (
        <Card
          allowRouteChange={allowRouteChange}
          article={article.item}
          key={index   "-main-"   props.articleGroup.category}
        />
      )}
    />
  );
}

const Card = (props) => {
  const [bookmark, setBookmark] = useState(props.article.bookmark);
  const cdnURL = wrapImgCDN(props.article.image, 400);

  const routeChange = async (event) => {
    if (props.allowRouteChange) {
           ... code to change route
    }
  };

  const _bookmarkToggle = () => {
    bookmarkToggle(props.article);
    setBookmark(!bookmark);
  };

  return (
    <Pressable
      onPress={routeChange}
    >
      <WhiteCard
        {...props}
        _bookmarkToggle={_bookmarkToggle}
        cdnURL={cdnURL}
        bookmark={bookmark}
      />
    </Pressable>
  );
};
  • Related