satya164 / react-native-tab-view

A cross-platform Tab View component for React Native
MIT License
5.13k stars 1.07k forks source link

tabs don't switch after a vertical gesture #809

Closed Flammae closed 5 years ago

Flammae commented 5 years ago

Bug

A "Quick start" code freezes on the selected tab after a vertical gesture on a TabView. The code there uses regular View elements but the same thing happens when you have scrollable components as a child.

Environment info

Device: Every Android Device (afaik)

Library version: ^2.6.0

Steps To Reproduce

Play with tabs. Switch with buttons. Switch with gestures. So far it works. Now make a vertical gesture like scrolling. The selected tab should freeze, buttons do not do anything until you change the tab by swiping.

Reproducible sample code

https://snack.expo.io/@beqa/tab-view-example-not-working

satya164 commented 5 years ago

Does this happen on latest version?

Your reproducible sample code doesn't have anything to repro the issue.

Flammae commented 5 years ago

I installed the latest version of tab view less than a week ago.

My point was that the bug is in the code that is provided in the documentation and it happens in the current release, as you can see in snack. I'd write more about my environment, but the exact same thing happens in the snack emulator as on my device

Flammae commented 5 years ago
React Native Environment Info:
    System:
      OS: Windows 10
      CPU: (4) x64 Intel(R) Core(TM) i7-3520M CPU @ 2.90GHz
      Memory: 2.85 GB / 7.87 GB
    Binaries:
      Yarn: 1.3.2 - ~\AppData\Roaming\npm\yarn.CMD
      npm: 5.8.0 - C:\Program Files\nodejs\npm.CMD
    IDEs:
      Android Studio: Version  3.4.0.0 AI-183.6156.11.34.5522156
satya164 commented 5 years ago

I installed the latest version of tab view less than a week ago

doesn't matter. there was a regression in the version you mentioned and you need to update to the latest version to check if it happens there

My point was that the bug is in the code that is provided in the documentation and it happens in the current release, as you can see in snack

The snack doesn't have a ScrollView like you mentioned in your issue. I'm not sure how I should test?

Flammae commented 5 years ago

just downloaded 2.6.1 build. The bug is still there.

about ScrollView, I was confusing for no reason. what I meant was I discovered the issue on scrollview and I spent much time debugging with ScrollView in mind, but I think problem here is TabView's gesture capture logic, because the bug also happens when you make swipe up gesture on a View component: <View style={[styles.scene, { backgroundColor: "#ff4081" }]} /> in the code, for example.

I've also not written swipe logic. I just interact with screen with swipe up gesture instead of swipe sideways on TabView component and it causes the TabBar buttons to not respond. the code I shared is all the code needed to reproduce the bug.

in the video I am sending, I change screens by swiping (first two interactions), than by clicking the buttons (second two), than I do swipe up on the pink screen and after that buttons no longer change tabs.

Screenrecorder-2019-06-18-23-32-22-474.zip

satya164 commented 5 years ago

I can't repro this on my Android device. Can you provide a git repo with the bug?

What device and Android version do you see it on?

oriharel commented 5 years ago

This happens to me too. Android only. tested on Pixel 2, Xiaomi A2, Samsung Galaxy S7. using tab-view 2.6.1

@beqaMeqvabishvili - What React Native version are you? Mine is 0.57. I'm going to try it on RN latest, will update.

oriharel commented 5 years ago

Some more insights - I just ran your snack, and the bug reproduces there. I created a react-native project with your demo, on RN 0.59.8, and the bug doesn't reproduce. So it's either expo causing this, or RN version that is different than 0.59.8.

satya164 commented 5 years ago

@osdnk could this be related to your recent changes?

satya164 commented 5 years ago

@oriharel can you check if this happens with 2.5.0?

calintamas commented 5 years ago

I'm on RN 0.59.9, 2.6.1 and the issue reproduces to me too.

oriharel commented 5 years ago

@calintamas - is this an expo project?

calintamas commented 5 years ago

@oriharel no

Tested with 2.5.0 too. Same thing.

oriharel commented 5 years ago

@satya164 - You got it!! doesn't reproduce on 2.5.0.

oriharel commented 5 years ago

@calintamas that's weird - I wasn't able to reproduce this outside of expo

calintamas commented 5 years ago

@oriharel on my side, it seems to happen on 2.5.0 too. Whenever I interact with the view (scroll, tap on an input, etc), tapping on the tab bar is no longer working

oriharel commented 5 years ago

@calintamas - are you sure you're on 2.5.0? (check with your yarn.lock file)

satya164 commented 5 years ago

thanks everyone for testing and providing more info. I think I have found the issue. I'll send a PR in a moment and it'll be great if someone could test if it works for them.

calintamas commented 5 years ago

@oriharel indeed, it was the cache. works on 2.5.0 🎉

satya164 commented 5 years ago

can you check if the change in #810 fixes it for you?

oriharel commented 5 years ago

@satya164 - can you help me with this? I tried to do:

"react-native-tab-view": "satya164/fix-tab-change#942a72d"

in package.json, but apparently it's not the way. how do I point to your commit?

satya164 commented 5 years ago

I think "react-native-tab-view": "github:react-native-community/react-native-tab-view#1cefb27" should be enough.

oriharel commented 5 years ago

This gives me error:

/react-native-tab-view/package.json was successfully found. However, this package itself specifies a main module field that could not be resolved (/Users/oriharel/code/kne.mobile/node_modules/react-native-tab-view/lib/module/index.js

satya164 commented 5 years ago

hmm weird.

you can copy this to node_modules/react-native-tab-view/lib/module/Pager.js

import _extends from"@babel/runtime/helpers/extends";import _objectSpread from"@babel/runtime/helpers/objectSpread";import _slicedToArray from"@babel/runtime/helpers/slicedToArray";import _classCallCheck from"@babel/runtime/helpers/classCallCheck";import _createClass from"@babel/runtime/helpers/createClass";import _possibleConstructorReturn from"@babel/runtime/helpers/possibleConstructorReturn";import _getPrototypeOf from"@babel/runtime/helpers/getPrototypeOf";import _inherits from"@babel/runtime/helpers/inherits";var _jsxFileName="/Users/satya/Workspace/Projects/react-native-tab-view/src/Pager.tsx";import*as React from'react';import{StyleSheet,Keyboard,I18nManager}from'react-native';import{PanGestureHandler,State}from'react-native-gesture-handler';import Animated,{Easing}from'react-native-reanimated';import memoize from'./memoize';var Clock=Animated.Clock,Value=Animated.Value,onChange=Animated.onChange,and=Animated.and,abs=Animated.abs,add=Animated.add,block=Animated.block,call=Animated.call,ceil=Animated.ceil,clockRunning=Animated.clockRunning,cond=Animated.cond,divide=Animated.divide,eq=Animated.eq,event=Animated.event,floor=Animated.floor,greaterThan=Animated.greaterThan,lessThan=Animated.lessThan,max=Animated.max,min=Animated.min,multiply=Animated.multiply,neq=Animated.neq,not=Animated.not,round=Animated.round,set=Animated.set,spring=Animated.spring,startClock=Animated.startClock,stopClock=Animated.stopClock,sub=Animated.sub,timing=Animated.timing;var TRUE=1;var FALSE=0;var NOOP=0;var UNSET=-1;var DIRECTION_LEFT=1;var DIRECTION_RIGHT=-1;var SWIPE_DISTANCE_MINIMUM=20;var SWIPE_VELOCITY_IMPACT=0.01;var SPRING_CONFIG={stiffness:1000,damping:500,mass:3,overshootClamping:true,restDisplacementThreshold:0.01,restSpeedThreshold:0.01};var TIMING_CONFIG={duration:200,easing:Easing.out(Easing.cubic)};var Pager=function(_React$Component){_inherits(Pager,_React$Component);function Pager(){var _getPrototypeOf2;var _this;_classCallCheck(this,Pager);for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++){args[_key]=arguments[_key];}_this=_possibleConstructorReturn(this,(_getPrototypeOf2=_getPrototypeOf(Pager)).call.apply(_getPrototypeOf2,[this].concat(args)));_this.clock=new Clock();_this.velocityX=new Value(0);_this.gestureX=new Value(0);_this.gestureState=new Value(State.UNDETERMINED);_this.offsetX=new Value(0);_this.progress=new Value(_this.props.navigationState.index*_this.props.layout.width*DIRECTION_RIGHT);_this.index=new Value(_this.props.navigationState.index);_this.nextIndex=new Value(UNSET);_this.lastEnteredIndex=new Value(_this.props.navigationState.index);_this.isSwiping=new Value(FALSE);_this.isSwipeGesture=new Value(FALSE);_this.routesLength=new Value(_this.props.navigationState.routes.length);_this.layoutWidth=new Value(_this.props.layout.width);_this.swipeVelocityImpact=new Value(_this.props.swipeVelocityImpact||SWIPE_VELOCITY_IMPACT);_this.position=cond(_this.layoutWidth,divide(multiply(_this.progress,-1),_this.layoutWidth),_this.index);_this.springConfig={damping:new Value(_this.props.springConfig.damping!==undefined?_this.props.springConfig.damping:SPRING_CONFIG.damping),mass:new Value(_this.props.springConfig.mass!==undefined?_this.props.springConfig.mass:SPRING_CONFIG.mass),stiffness:new Value(_this.props.springConfig.stiffness!==undefined?_this.props.springConfig.stiffness:SPRING_CONFIG.stiffness),restSpeedThreshold:new Value(_this.props.springConfig.restSpeedThreshold!==undefined?_this.props.springConfig.restSpeedThreshold:SPRING_CONFIG.restSpeedThreshold),restDisplacementThreshold:new Value(_this.props.springConfig.restDisplacementThreshold!==undefined?_this.props.springConfig.restDisplacementThreshold:SPRING_CONFIG.restDisplacementThreshold)};_this.timingConfig={duration:new Value(_this.props.timingConfig.duration!==undefined?_this.props.timingConfig.duration:TIMING_CONFIG.duration)};_this.initialVelocityForSpring=new Value(0);_this.currentIndexValue=_this.props.navigationState.index;_this.pendingIndexValue=undefined;_this.enterListeners=[];_this.jumpToIndex=function(index){_this.isSwipeGesture.setValue(FALSE);_this.nextIndex.setValue(index);};_this.jumpTo=function(key){var navigationState=_this.props.navigationState;var index=navigationState.routes.findIndex(function(route){return route.key===key;});if(navigationState.index===index){_this.jumpToIndex(index);}else{_this.props.onIndexChange(index);}};_this.addListener=function(type,listener){switch(type){case'enter':_this.enterListeners.push(listener);break;}};_this.removeListener=function(type,listener){switch(type){case'enter':{var _index=_this.enterListeners.indexOf(listener);if(_index>-1){_this.enterListeners.splice(_index,1);}break;}}};_this.handleEnteredIndexChange=function(_ref){var _ref2=_slicedToArray(_ref,1),value=_ref2[0];var index=Math.max(0,Math.min(value,_this.props.navigationState.routes.length-1));_this.enterListeners.forEach(function(listener){return listener(index);});};_this.transitionTo=function(index){var toValue=new Value(0);var frameTime=new Value(0);var state={position:_this.progress,time:new Value(0),finished:new Value(FALSE)};return block([cond(clockRunning(_this.clock),NOOP,[set(toValue,multiply(index,_this.layoutWidth,DIRECTION_RIGHT)),set(frameTime,0),set(state.time,0),set(state.finished,FALSE),set(_this.index,index),startClock(_this.clock)]),cond(_this.isSwipeGesture,[cond(not(clockRunning(_this.clock)),I18nManager.isRTL?set(_this.initialVelocityForSpring,multiply(-1,_this.velocityX)):set(_this.initialVelocityForSpring,_this.velocityX)),spring(_this.clock,_objectSpread({},state,{velocity:_this.initialVelocityForSpring}),_objectSpread({},SPRING_CONFIG,_this.springConfig,{toValue:toValue}))],timing(_this.clock,_objectSpread({},state,{frameTime:frameTime}),_objectSpread({},TIMING_CONFIG,_this.timingConfig,{toValue:toValue}))),cond(state.finished,[set(_this.isSwipeGesture,FALSE),set(_this.gestureX,0),set(_this.velocityX,0),stopClock(_this.clock)])]);};_this.handleGestureEvent=event([{nativeEvent:{translationX:_this.gestureX,velocityX:_this.velocityX,state:_this.gestureState}}]);_this.velocitySignum=cond(_this.velocityX,divide(abs(_this.velocityX),_this.velocityX),0);_this.extrapolatedPosition=add(_this.gestureX,multiply(_this.velocityX,_this.velocityX,_this.velocitySignum,_this.swipeVelocityImpact));_this.translateX=block([onChange(_this.index,call([_this.index],function(_ref3){var _ref4=_slicedToArray(_ref3,1),value=_ref4[0];_this.currentIndexValue=value;if(value!==_this.props.navigationState.index){_this.props.onIndexChange(value);_this.pendingIndexValue=value;_this.forceUpdate();}})),onChange(_this.position,cond(I18nManager.isRTL?lessThan(_this.gestureX,0):greaterThan(_this.gestureX,0),cond(neq(floor(_this.position),_this.lastEnteredIndex),[set(_this.lastEnteredIndex,floor(_this.position)),call([floor(_this.position)],_this.handleEnteredIndexChange)]),cond(neq(ceil(_this.position),_this.lastEnteredIndex),[set(_this.lastEnteredIndex,ceil(_this.position)),call([ceil(_this.position)],_this.handleEnteredIndexChange)]))),onChange(_this.isSwiping,call([_this.isSwiping],function(_ref5){var _ref6=_slicedToArray(_ref5,1),value=_ref6[0];var _this$props=_this.props,keyboardDismissMode=_this$props.keyboardDismissMode,onSwipeStart=_this$props.onSwipeStart,onSwipeEnd=_this$props.onSwipeEnd;if(value===TRUE){onSwipeStart&&onSwipeStart();if(keyboardDismissMode==='on-drag'){Keyboard.dismiss();}}else{onSwipeEnd&&onSwipeEnd();}})),onChange(_this.nextIndex,cond(neq(_this.nextIndex,UNSET),[cond(clockRunning(_this.clock),stopClock(_this.clock)),set(_this.gestureX,0),set(_this.index,_this.nextIndex),set(_this.nextIndex,UNSET)])),cond(eq(_this.gestureState,State.ACTIVE),[cond(_this.isSwiping,NOOP,[set(_this.isSwiping,TRUE),set(_this.isSwipeGesture,TRUE),set(_this.offsetX,_this.progress)]),set(_this.progress,I18nManager.isRTL?sub(_this.offsetX,_this.gestureX):add(_this.offsetX,_this.gestureX)),stopClock(_this.clock)],[set(_this.isSwiping,FALSE),_this.transitionTo(cond(and(_this.gestureX,greaterThan(abs(_this.extrapolatedPosition),divide(_this.layoutWidth,2))),round(min(max(0,sub(_this.index,cond(greaterThan(_this.velocitySignum,0),I18nManager.isRTL?DIRECTION_RIGHT:DIRECTION_LEFT,I18nManager.isRTL?DIRECTION_LEFT:DIRECTION_RIGHT))),sub(_this.routesLength,1))),_this.index))]),_this.progress]);_this.getTranslateX=memoize(function(layoutWidth,routesLength,translateX){return multiply(min(max(multiply(layoutWidth,sub(routesLength,1),DIRECTION_RIGHT),translateX),0),I18nManager.isRTL?-1:1);});return _this;}_createClass(Pager,[{key:"componentDidUpdate",value:function componentDidUpdate(prevProps){var _this$props2=this.props,navigationState=_this$props2.navigationState,layout=_this$props2.layout,swipeVelocityImpact=_this$props2.swipeVelocityImpact,springConfig=_this$props2.springConfig,timingConfig=_this$props2.timingConfig;var index=navigationState.index,routes=navigationState.routes;if(index!==prevProps.navigationState.index&&index!==this.currentIndexValue||typeof this.pendingIndexValue==='number'&&index!==this.pendingIndexValue){this.jumpToIndex(index);}this.pendingIndexValue=undefined;if(prevProps.navigationState.routes.length!==routes.length){this.routesLength.setValue(routes.length);}if(prevProps.layout.width!==layout.width){this.progress.setValue(-index*layout.width);this.layoutWidth.setValue(layout.width);}if(prevProps.swipeVelocityImpact!==swipeVelocityImpact){this.swipeVelocityImpact.setValue(swipeVelocityImpact!=null?swipeVelocityImpact:SWIPE_VELOCITY_IMPACT);}if(prevProps.springConfig!==springConfig){this.springConfig.damping.setValue(springConfig.damping!==undefined?springConfig.damping:SPRING_CONFIG.damping);this.springConfig.mass.setValue(springConfig.mass!==undefined?springConfig.mass:SPRING_CONFIG.mass);this.springConfig.stiffness.setValue(springConfig.stiffness!==undefined?springConfig.stiffness:SPRING_CONFIG.stiffness);this.springConfig.restSpeedThreshold.setValue(springConfig.restSpeedThreshold!==undefined?springConfig.restSpeedThreshold:SPRING_CONFIG.restSpeedThreshold);this.springConfig.restDisplacementThreshold.setValue(springConfig.restDisplacementThreshold!==undefined?springConfig.restDisplacementThreshold:SPRING_CONFIG.restDisplacementThreshold);}if(prevProps.timingConfig!==timingConfig){this.timingConfig.duration.setValue(timingConfig.duration!==undefined?timingConfig.duration:TIMING_CONFIG.duration);}}},{key:"render",value:function render(){var _this2=this;var _this$props3=this.props,layout=_this$props3.layout,navigationState=_this$props3.navigationState,swipeEnabled=_this$props3.swipeEnabled,children=_this$props3.children,removeClippedSubviews=_this$props3.removeClippedSubviews,gestureHandlerProps=_this$props3.gestureHandlerProps;var translateX=this.getTranslateX(this.layoutWidth,this.routesLength,this.translateX);return children({position:this.position,addListener:this.addListener,removeListener:this.removeListener,jumpTo:this.jumpTo,render:function render(children){return React.createElement(PanGestureHandler,_extends({enabled:layout.width!==0&&swipeEnabled,onGestureEvent:_this2.handleGestureEvent,onHandlerStateChange:_this2.handleGestureEvent,activeOffsetX:[-SWIPE_DISTANCE_MINIMUM,SWIPE_DISTANCE_MINIMUM],failOffsetY:[-SWIPE_DISTANCE_MINIMUM,SWIPE_DISTANCE_MINIMUM]},gestureHandlerProps,{__source:{fileName:_jsxFileName,lineNumber:602}}),React.createElement(Animated.View,{removeClippedSubviews:removeClippedSubviews,style:[styles.container,layout.width?{width:layout.width*navigationState.routes.length,transform:[{translateX:translateX}]}:null],__source:{fileName:_jsxFileName,lineNumber:610}},children));}});}}]);return Pager;}(React.Component);Pager.defaultProps={swipeVelocityImpact:SWIPE_VELOCITY_IMPACT};export{Pager as default};var styles=StyleSheet.create({container:{flex:1,flexDirection:'row'}});
//# sourceMappingURL=Pager.js.map
Flammae commented 5 years ago

@satya164 I copied changes from your commit by hand, it does not seem to fix the issue

satya164 commented 5 years ago

where did you copy it? the published package uses compiled code. my commit has uncompiled code.

Flammae commented 5 years ago

node_modules/react-native-tab-view/package.json and node_modules/react-native-tab-view/src/Pager.tsx

satya164 commented 5 years ago

@beqaMeqvabishvili that won't work since metro will use the compiled version. please copy according to my comment https://github.com/react-native-community/react-native-tab-view/issues/809#issuecomment-503454465

mgcostaParedes commented 5 years ago

tried to install from this repo github:react-native-community/react-native-tab-view#942a72d gives me the follow error: npm ERR! Command failed: C:\Program Files\Git\cmd\git.EXE checkout 942a72d npm ERR! error: pathspec '942a72d' did not match any file(s) known to git.

Flammae commented 5 years ago

oh ok. no need to change anything in my project's package.json?

Flammae commented 5 years ago

@satya164 It worked! thank you

Jakob-Koschel commented 5 years ago

I had the same issue on 2.5.0, 2.6.0 and 2.6.1 with RN 0.59.5 and no expo. The changes in your comment fixed it (https://github.com/react-native-community/react-native-tab-view/issues/809#issuecomment-503454465)

Will there be a new version including this fix soon?

satya164 commented 5 years ago

I'm waiting for a review from @osdnk. We'll do a release after.

For now you can downgrade to 2.5.0 which doesn't have the issue. If you are experiencing the issue with that version, then try clearing cache (react-native start --reset-cache or expo start -c).

I've also set 2.5.0 as latest on npm so any new installs will install that version.