shet_tayyy
shet_tayyy

Reputation: 5755

React Native: How to disable PanResponder temporarily?

Below is the snippet for creating an instance of the panResponder:

  constructor( props ) {
    super( props );

    this.position = new Animated.ValueXY();

    this.panResponder = PanResponder.create( {
      onStartShouldSetPanResponder: ( ) => true,
      onPanResponderMove: ( event, gesture ) => {
        this.position.setValue( { x: 0, y: gesture.dy } );
      },
      onPanResponderRelease: ( event, gesture ) => {
        if ( gesture.dy > SWIPE_THRESHOLD ) {
          this.forceSwipe( 'up' );
        } else if ( gesture.dy < -SWIPE_THRESHOLD ) {
          this.forceSwipe( 'down' );
        } else {
          this.resetPosition();
        }
      },
      onMoveShouldSetPanResponderCapture: ( evt, gestureState ) =>
      gestureState.dx !== 0 && gestureState.dy !== 0,
    } );
  }

  getCardStyle() {
    return {
      ...this.position.getLayout(),
    };
  }

Below is the code snippet of my Component:

<Animated.View
   key={ item.id }
   style={ [ this.getCardStyle(), styles.cardStyle ] }
   { ...this.panResponder.panHandlers }
>
  <Card
    shouldPanRespond={ this.shouldPanRespond }
     item={ item }
  />
</Animated.View>

Code snippet for card component:

  <TouchableOpacity
    activeOpacity={ 1 }
    style={ cardStyle }
    onPress={ this.onPress }
  >
    <ScrollView contentContainerStyle={ cardStyle }>
      <Image
        resizeMode="cover"
        style={ imageStyle }
        source={ { uri: img_url } }
      />

      <View style={ titleWrapStyle }>
        <Text style={ textStyle }>{ title }</Text>
      </View>
    </ScrollView>
  </TouchableOpacity>

I am building cards that are absolutely positioned one behind another. These cards can then be swiped up or down using the panResponder. But, on tapping the screen I am supposed to show a detail page on the same screen.

Now, I can detect the tap, but whenever the user taps/clicks, I want to disable the panResponder so that the card cannot be swiped and the user can scroll through the content. Once, the user scrolls to the end, I want to re-enable swiping i.e I want to enable panning.

I know that onStartShouldSetResponder: () => false disables panResponder but how do we disable it after an instance is created. I couldn't find much about it else where.

Upvotes: 6

Views: 9821

Answers (4)

Quantum Ninja
Quantum Ninja

Reputation: 23

If you want a really simple brute force solution use the pointerEvents prop on the animated view.

pointerEvents={stopPan ? 'none' : 'auto'}

This will stop all touch events on the view and all below it firing while stopPan is true.

Upvotes: 2

mathycoder
mathycoder

Reputation: 11

I think I found a way to disable PanResponder temporarily using onStartShouldSetResponder. The goal is to toggle between true and false, even though it doesn't seem to respond to changes in state values. If you're using functional components, the hook useRef seems to do the trick.

const [panResponderEnabled, _setPanResponderEnabled] = useState(true)

const panResponderEnabledRef = useRef(panResponderEnabled)
const setPanResponderEnabled = data => {
  panResponderEnabledRef.current = data
  _setPanResponderEnabled(data)
}

Then when you create your PanResponder:

onStartShouldSetPanResponder: () => panResponderEnabledRef.current,

Upvotes: 1

bumbur
bumbur

Reputation: 311

For someone who will be looking how to temporarily disable PanResponder, below is my solution.

Like @RaHuL - House Javascript noticed, the other answer doesn't really solve the problem because setting onStartShouldSetResponder to value corresponding to some state value which might change from true to false won't affect PanResponder (at least it seems so) if it was created with true.

I solved similar problem by changing onPanResponderMove function. I have a value inside state: panResponderEnabled and my function looks like this:

 onPanResponderMove: ( event, gesture ) => {
    if(this.state.panResponderEnabled){
       this.position.setValue( { x: 0, y: gesture.dy } ); 
    }
}

When I don't want to read changes from PanRespoder I just set panResponderEnabled to false.

Upvotes: 5

agenthunt
agenthunt

Reputation: 8678

Instead of the arrow function for onStartShouldSetResponder you can use class function and use state to return true/false. Something like this

 constructor( props ) {
    super( props );
    this.state = {
      panResponderEnabled: true
    }
    this.position = new Animated.ValueXY();

    this.panResponder = PanResponder.create( {
      onStartShouldSetPanResponder: ( ) => true,
      onPanResponderMove: ( event, gesture ) => {
        this.position.setValue( { x: 0, y: gesture.dy } );
      },
      onPanResponderRelease: ( event, gesture ) => {
        if ( gesture.dy > SWIPE_THRESHOLD ) {
          this.forceSwipe( 'up' );
        } else if ( gesture.dy < -SWIPE_THRESHOLD ) {
          this.forceSwipe( 'down' );
        } else {
          this.resetPosition();
        }
      },
      onMoveShouldSetPanResponderCapture: ( evt, gestureState ) =>
      gestureState.dx !== 0 && gestureState.dy !== 0,
    } );
  }

  handlePanResponderStart = () => {
    return this.state.panResponderEnabled;
  }
  onCardPress = () => {
    this.setState({
      panResponderEnabled: false
    });
  }
  getCardStyle() {
    return {
      ...this.position.getLayout(),
    };
  }

Upvotes: -1

Related Questions