Arun Shankar
Arun Shankar

Reputation: 1

onPanResponderRelease not being triggered

I am trying to use the PanResponder on a View. The onStartShouldSetPanResponder and onMoveShouldSetPanResponder but onPanResponderMove, onPanResponderGrant and onPanResponderRelease does not get triggered at all. My react and react native versions are:

"react": "^15.2.1",
"react-native": "^0.30.0",

Below is the code

'use strict'
import React from 'react'
const Icon = require('react-native-vector-icons/Ionicons')
let THUMB_URLS = require('../Statics/ListingsData.js')
let SidePanelComponent = require('./common/SidePanel.js')
let RecentSearches = require('./Views/RecentSearches/RecentSearches.js')
let TimerMixin = require('react-timer-mixin')
const Loader = require('./common/LoadingState.js')
import { getImageURL, getUserImageURL } from './_helpers/images'

const config = require('../config')
import GoogleAnalytics from 'react-native-google-analytics-bridge'
GoogleAnalytics.setTrackerId(config.google_analytics_id)

const windowSize = require('Dimensions').get('window')
const deviceWidth = windowSize.width
const deviceHeight = windowSize.height

import {
  Image,
  Text,
  View,
  TouchableOpacity,
  TouchableWithoutFeedback,
  ScrollView,
  StyleSheet,
  Platform,
  Animated,
  PanResponder
} from 'react-native'

let LISTINGS = []

const ListingsViewComponent = React.createClass({
  mixins: [TimerMixin],

  getInitialState: function () {
    return {
      listings: [],
      dataSource: [],
      showSearchIcon: false,
      showSidePanel: false,
      photo: {},
      componentloading: true,
      showHeartIcon: [],
      startX: 0,
      startY: 0,
      showWishlistMenu: false,
      wishlistCurrentY: 0,
      showNewWishlistTextInput: false,
      currentRowdata: {},
      wishlistOptions: [],
      showrecentsearches: false,
      isDataLoading: true,
      scrolling: false,
      _listViewDirtyPressEnabled: true,
      scrollAnimationEnd: false,
      scrollStates: [],
      goingtonextview: false,
      heroImageContainerHeight: deviceWidth,
      searchbar: new Animated.ValueXY()
    }
  },

  _panListingsResponder: {},

  componentWillMount: function () {
    this._panListingsResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, g) => {
        this.setState({
          startX: e.nativeEvent.pageX,
          startY: e.nativeEvent.pageY
        })
      },
      onStartShouldSetPanResponderCapture: (e, g) => {
      },
      onMoveShouldSetPanResponder: (e, g) => {
        this.setState({
          heroImageContainerHeight: deviceWidth - (e.nativeEvent.pageY - this.state.startY)
        })
      },
      onMoveShouldSetPanResponderCapture: (e, g) => {},
      onPanResponderGrant: (e, g) => {},
      onPanResponderMove: (e, g) => {
        this.setState({
          heroImageContainerHeight: deviceWidth - (e.nativeEvent.pageY - this.state.startY)
        })
      },
      onPanResponderTerminationRequest: (e, g) => {
        console.log('onPanResponderTerminationRequest', e.nativeEvent)
        return false
      },
      onPanResponderRelease: (e, g) => {
        console.log('_onResponderRelease', e.nativeEvent)
      },
      onPanResponderTerminate: (e, g) => {
        console.log('onPanResponderTerminate', e.nativeEvent)
      },
      onShouldBlockNativeResponder: (e, g) => true
    })

    let listingsendpoint = 'http://faithstay-staging.herokuapp.com/api/listings'
    this.setState({
      isDataLoading: true
    })

    fetch(listingsendpoint)
      .then((response) => response.json())
      .then((listingsData) => {
        const listings = listingsData
        LISTINGS = []
        LISTINGS.push(THUMB_URLS[0])
        LISTINGS.push(THUMB_URLS[1])
        LISTINGS.push(THUMB_URLS[2])

        listings.map((listing) => {
          LISTINGS.push(listing)
        })

        this.setState({
          isDataLoading: false,
          listings: LISTINGS
        })
      })
      .catch((error) => {
        console.warn(error)
      })
  },

  componentDidMount: function () {
    GoogleAnalytics.trackScreenView('Faithstay-Listings-Page')
  },

  _showSidePanel: function () {
    this.setState({
      showSidePanel: true
    })
  },

  _closeSidePanel: function () {
    this.setState({
      showSidePanel: false
    })
  },

  _showRecentSearches: function () {
    this.setState({
      showrecentsearches: true
    })
  },

  _closeRecentSearches: function () {
    this.setState({
      showrecentsearches: false
    })
  },

  componentWillReceiveProps: function () {
    this.setState({
      goingtonextview: false
    })
  },

  getSearchBarStyle: function () {
    return [
      styles.searchbar, {
        top: this.state.heroImageContainerHeight
      }
    ]
  },

  render: function () {
    let sidePanelViewContainer
    if (this.state.showSidePanel) {
      sidePanelViewContainer = (<SidePanelComponent {...this.props} imageuri={this.state.photo} onClose={this._closeSidePanel} />)
    }

    let searchIconContainer = <Animated.View style={this.getSearchBarStyle()}>
      <TouchableOpacity style={styles.searchBarInner} onPress={this._showRecentSearches}>
        <Text style={styles.searchtext}>
          {'Where do you want to go?'}
        </Text>
        <Icon
          name={'ios-search'}
          size={30}
          color={'#cfcfcf'}
          style={styles.searchicon}
        />
      </TouchableOpacity>
    </Animated.View>

    if (!this.state.showrecentsearches) {
      if (this.state.isDataLoading) {
        return (<Loader />)
      } else {
        return (
          <View style={styles.container} {...this._panListingsResponder.panHandlers}>
            <View style={[styles.heroImageContainer, { height: this.state.heroImageContainerHeight }]}>
              <Image source={{uri: 'https://faithstay-statics.imgix.net/images/homepage_carousel_4.jpg'}} style={[styles.heroImage, { height: this.state.heroImageContainerHeight }]} />
              <View style={[styles.scrimLayer, { height: this.state.heroImageContainerHeight }]} />
              <View style={styles.logoContainer}>
                <Image source={require('../Statics/images/anchor_3x.png')} style={styles.logoImage} />
                <Text style={styles.logoText}>{'FaithStay'}</Text>
              </View>
              <View style={styles.horizontalDivider} />
              <View style={styles.betaVersionContainer}>
                <Text style={styles.betaVersionText}>{'Beta Version'}</Text>
              </View>
              <View style={[styles.pageTitleContainer, {top: this.state.heroImageContainerHeight - 85}]}>
                <Text style={styles.pageTitle}>{'Home'}</Text>
              </View>
              <View style={[styles.movableScrim, {backgroundColor: `rgba(0, 0, 0, ${(deviceWidth - this.state.heroImageContainerHeight) / deviceWidth})`}]} />
            </View>
            {searchIconContainer}
            <ScrollView style={styles.listView}>
              {this.getListingsView()}
            </ScrollView>
            {sidePanelViewContainer}
          </View>
        )
      }
    }
    return (<RecentSearches {...this.props} closeRecentSearches={this._closeRecentSearches} />)
  },

  _gotoUserProfilePage: function (user) {
    this.props.navigator.push({
      id: 15,
      passProps: {
        user
      }
    })
  },

  getListingsView: function () {
    let listings = this.state.listings
    const listingsArray = []
    listings.map((listing, i) => {
      let currentlisting = listing
      let type = currentlisting.type

      if (type !== 'NOT_A_LISTING') {
        let imgSource = {
          uri: getImageURL(currentlisting.images[0])
        }

        let profileimg = {
          uri: getUserImageURL(currentlisting.host)
        }

        let title = currentlisting.title
        let reviews = '18'
        let address_values = currentlisting.google_place.formatted_address ? currentlisting.google_place.formatted_address.split(',') : []
        let listing_address = {}

        if (address_values.length > 0) {
          listing_address = {
            country: address_values[address_values.length - 1].trim(),
            state: address_values[address_values.length - 2].trim(),
            city: address_values[address_values.length - 3].trim()
          }
        }

        let city = listing_address.city + ', ' + listing_address.state
        let baseprice = currentlisting.base_price ? '$' + currentlisting.base_price : '0'

        listingsArray.push(<View>
          <TouchableWithoutFeedback onPress={() => this._pressRow(currentlisting)}>
              <View>
                <View style={styles.row}>
                  <Image style={styles.thumb} source={imgSource} >
                    <View style={styles.priceconatiner}>
                      <Text style={styles.pricetext}>{baseprice}</Text>
                    </View>
                  </Image>
                </View>
                <TouchableOpacity style={styles.profileImgContainer} onPress={() => this._gotoUserProfilePage(currentlisting.host)}>
                  <Image style={styles.profileimg} source={profileimg} />
                </TouchableOpacity>
                <View style={styles.listingtextcontainer}>
                  <Text style={styles.listingtexttitle}>{title}</Text>
                  <Text style={styles.listingtexttdescription}>{'Entire Home' + ' - ' + reviews + ' Reviews' + ' - ' + city}</Text>
                </View>
            </View>
          </TouchableWithoutFeedback>
        </View>)
      } else {
        let listing_title = listing.title
        let listing_description = listing.description
        let imageuri = listing.image;
        listingsArray.push(<View><TouchableWithoutFeedback onPress={() => this._pressNonListingRow(currentlisting)}>
            <View>
                <View style={styles.rowNotListing}>
                  <Image style={styles.thumbNotListing} source={{uri: imageuri}}>
                    <View style={styles.thumbNotListing, {position: 'absolute', left:0, top: 0, right:0, bottom:0, backgroundColor: 'rgba(0,0,0,0.2)'}} >
                    </View>
                    <View style={styles.thumbNotListingSubContainer}>
                      <Text style={styles.listingtitle_notlisting}>{listing_title}</Text>
                      <Text style={styles.listingdescription_notlisting}>{listing_description}</Text>
                    </View>
                  </Image>
                </View>
            </View>
          </TouchableWithoutFeedback>
        </View>)
      }
    })
    return listingsArray
  },

  _pressRow: function (listing) {
    this.props.navigator.push({
      id: 4,
      passProps: {
        listingdata: listing
      }
    })
  },

  _pressNonListingRow: function (listing) {
    this.props.navigator.push({
      id: 9,
      passProps: {
        filterData: listing
      }
    })
  }
})

const paddingHorizontal = 15
const paddingVertical = 10
const distanceBetweenIcons = (deviceWidth - 115) / 3
const statusBarHeight = (Platform.OS === 'ios') ? 20 : 0

const isAndroid = Platform.OS === 'android'

const styles = StyleSheet.create({
  listView: {
    height: deviceHeight - 70,
    top: (Platform.OS === 'ios') ? 40 : 0,
    left: 0
  },
  scrimLayer: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: deviceWidth,
    height: deviceWidth,
    backgroundColor: 'rgba(0, 0, 0, 0.2)'
  },
  movableScrim: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: deviceWidth,
    height: deviceWidth
  },
  container: {
    flex: 1,
    paddingTop: statusBarHeight,
    width: deviceWidth,
    height: deviceHeight
  },

  row: {
    flexDirection: 'row',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    width: deviceWidth,
    height: deviceHeight / 2
  },

  separator: {
    height: 1,
    backgroundColor: '#CCCCCC'
  },

  thumb: {
    width: deviceWidth,
    height: deviceHeight / 2 - 80
  },

  thumbNotListing: {
    width: deviceWidth,
    height: deviceHeight / 2,
    justifyContent: 'center'
  },

  thumbNotListingSubContainer: {
    alignSelf: 'center',
    justifyContent: 'center'
  },

  listingtitle_notlisting: {
    textAlign: 'center',
    alignSelf: 'center',
    fontSize: 24,
    fontWeight: 'bold',
    color: '#ffffff'
  },

  listingdescription_notlisting: {
    textAlign: 'center',
    alignSelf: 'center',
    fontSize: 16,
    marginTop: 10,
    color: '#ffffff'
  },

  text: {
    flex: 1,
  },

  tabbar: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    width: deviceWidth,
    height: 49,
    backgroundColor: '#f5f5f5',
    justifyContent: 'space-between',
    borderTopWidth: 1,
    borderTopColor: '#dce0e0'
  },

  searchbar: {
    width: deviceWidth - 30,
    height: 50,
    left: 15,
    top: deviceWidth - 5,
    position: 'absolute',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    shadowOpacity: 0.5
  },
  searchBarInner: {
    width: deviceWidth - 30,
    height: 50,
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    shadowOpacity: 0.5
  },
  searchonlyicon: {
    width: 50,
    height: 50,
    borderRadius: 25,
    left: 20,
    top: 40,
    position: 'absolute',
    justifyContent: 'center',
    backgroundColor: '#f5f5f5',
    shadowOpacity: 0.5
  },
  searchtext: {
    width: 160,
    position: 'absolute',
    fontSize: 15,
    color: '#565a5c',
    left: (deviceWidth - 30) / 2 - 80,
    top: 15,
    fontFamily: 'RobotoCondensed-Regular'
  },
  searchicon: {
    width: 30,
    height: 30,
    position: 'absolute',
    top: 8,
    left: 12
  },
  homeicon: {
    width: 30,
    height: 30,
    position: 'absolute',
    top: paddingVertical - 2,
    left: paddingHorizontal,
    justifyContent: 'center',
  },
  hearticon: {
    width: 40,
    height: 30,
    position: 'absolute',
    top: paddingVertical,
    justifyContent: 'center',
    left: distanceBetweenIcons
  },
  emailicon: {
    width: 45,
    height: 30,
    position: 'absolute',
    top: paddingVertical,
    justifyContent: 'center',
    left: 2 * distanceBetweenIcons
  },
  bagicon: {
    width: 35,
    height: 20,
    position: 'absolute',
    top: paddingVertical + 6,
    justifyContent: 'center',
    left: 3 * distanceBetweenIcons
  },
  personicon: {
    width: 30,
    height: 30,
    position: 'absolute',
    top: paddingVertical,
    justifyContent: 'center',
    right: paddingHorizontal
  },
  priceconatiner: {
    position: 'absolute',
    top: deviceHeight / 2 - 150,
    left: 0,
    width: 60,
    height: 40,
    backgroundColor: 'rgba(60,63,64,0.9)',
    justifyContent: 'center'
  },
  pricetext: {
    fontSize: 20,
    color: '#fff',
    fontWeight: 'bold',
    textAlign: 'center',
    width: 60,
    fontFamily: 'HelveticaNeue'
  },
  profileImgContainer: {
    position: 'absolute',
    top: deviceHeight / 2 - 108,
    right: isAndroid ? 0 : 20,  // NOTE: add to width, vs pushing it with position values
    width: isAndroid ? 70 : 50, // NOTE: on android, the view must be as big as the image, otherwise the image will be cut off
    height: 50,
    paddingLeft: paddingHorizontal,
    justifyContent: 'center'
  },
  profileimg: {
    width: 50,
    height: 50,
    borderRadius: 25
  },
  listingtextcontainer: {
    position: 'absolute',
    top: deviceHeight / 2 - 70,
    left: paddingHorizontal,
    justifyContent: 'space-between',
    height: 50
  },
  listingtexttitle: {
    paddingTop: 5,
    fontSize: 16,
    fontFamily: 'HelveticaNeue',
    color: '#565a5c',
    fontWeight: 'bold'
  },
  listingtexttdescription: {
    fontSize: 14,
    fontFamily: 'HelveticaNeue',
    color: '#82888a',
    paddingBottom: 5
  },
  wishlistIcon: {
    position: 'absolute',
    right: 20,
    top: 20
  },
  hearticonwishlist: {
    width: 30,
    height: 30
  },
  wishlistScrollView: {
    position: 'absolute',
    right: 20,
    width: deviceWidth - 60,
    height: 80,
    backgroundColor: '#fff'
  },
  scrollRow: {
    width: 180,
    height: 40,
    justifyContent: 'center',
    padding: 5,
    borderBottomWidth: 1,
    borderBottomColor: '#f5f5f5'
  },
  wishlistScrollViewContainer: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0,
    width: deviceWidth,
    height: deviceHeight,
    backgroundColor: 'rgba(255,255,255,0.1)'
  },

  touchableScrollViewContainer: {
    width: deviceWidth,
    height: deviceHeight,
    position: 'absolute',
    top: 0,
    bottom: 0,
    left: 0
  },

  fontWishlistScroller: {
    color: '#565a5c',
    fontSize: 14
  },

  rowNotListing: {
    flexDirection: 'row',
    justifyContent: 'center',
    width: deviceWidth,
    height: deviceHeight / 2
  },
  heroImageContainer: {
    width: deviceWidth,
    height: deviceWidth
  },
  heroImage: {
    width: deviceWidth,
    height: deviceWidth
  },
  logoContainer: {
    width: 120,
    position: 'absolute',
    left: (deviceWidth / 2) - 60,
    top: 19,
    flexDirection: 'row',
    justifyContent: 'center',
    backgroundColor: 'transparent'
  },
  logoImage: {
    width: 18,
    height: 30,
    top: 3
  },
  logoText: {
    fontFamily: 'RobotoCondensed-Regular',
    fontSize: 25,
    fontWeight: '400',
    textAlign: 'center',
    color: '#fffff0',
    marginLeft: 7.7
  },
  horizontalDivider: {
    width: 32,
    position: 'absolute',
    left: deviceWidth / 2 - 16,
    top: 59,
    borderBottomWidth: 1,
    borderColor: '#ffffff'
  },
  betaVersionContainer: {
    width: 120,
    position: 'absolute',
    left: deviceWidth / 2 - 60,
    top: 79,
    justifyContent: 'center',
    backgroundColor: 'transparent'
  },
  betaVersionText: {
    fontFamily: 'RobotoCondensed-Regular',
    fontSize: 14,
    fontStyle: 'italic',
    fontWeight: '300',
    textAlign: 'center',
    color: '#ffffff',
    alignSelf: 'center'
  },
  pageTitleContainer: {
    position: 'absolute',
    top: deviceWidth - 85,
    left: 20,
    backgroundColor: 'transparent'
  },
  pageTitle: {
    fontSize: 34,
    fontFamily: 'RobotoCondensed-Bold',
    color: '#ffffff'
  }
})

module.exports = ListingsViewComponent

Upvotes: 0

Views: 4171

Answers (2)

Umang Loriya
Umang Loriya

Reputation: 878

I got it working properly by using onPanResponderEnd instead of onPanResponderRelease.

Also if we still want to use onPanResponderRelease then we should allow termination request by:

onPanResponderTerminationRequest: () => true

Upvotes: 1

Kevin Li
Kevin Li

Reputation: 2114

You need to make sure the following handlers return true

      onStartShouldSetPanResponder: (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
      onMoveShouldSetPanResponder: (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,

The only different between onStart... and onMove... is that the PanResponder will be created when you start rendering the component for onStart..., it will be created (lazy) when user start tab or move for onMove.

On android side, you may still find that onPanResponderRelease will not be triggered, an issue reported here as well https://github.com/facebook/react-native/issues/9447

I ended up using onPanResponderTerminate to handle this case. Hopefully you can get more insights about it.

Upvotes: 0

Related Questions