From f263aaf8fc5b115ba024df83e82787b2e141a67a Mon Sep 17 00:00:00 2001 From: Brent Vatne Date: Sun, 25 Feb 2018 16:33:20 -0800 Subject: [PATCH] Pop to top on TabNavigator when tapping already focused tab icon (#3589) * Pop to top when tapping the tab icon for an already focused tab * Dispatch popToTop with key to scope the action properly * Add test for POP_TO_TOP with key --- .../react-navigation/src/NavigationActions.js | 1 + .../src/routers/StackRouter.js | 6 ++++ .../src/routers/__tests__/StackRouter-test.js | 29 +++++++++++++++++++ .../src/views/TabView/TabBarBottom.js | 28 ++++++++++++++++-- .../src/views/TabView/TabBarTop.js | 10 ++++++- 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/packages/react-navigation/src/NavigationActions.js b/packages/react-navigation/src/NavigationActions.js index cff81a97..52f644b4 100644 --- a/packages/react-navigation/src/NavigationActions.js +++ b/packages/react-navigation/src/NavigationActions.js @@ -57,6 +57,7 @@ const pop = createAction(POP, payload => ({ const popToTop = createAction(POP_TO_TOP, payload => ({ type: POP_TO_TOP, immediate: payload && payload.immediate, + key: payload && payload.key, })); const push = createAction(PUSH, payload => { diff --git a/packages/react-navigation/src/routers/StackRouter.js b/packages/react-navigation/src/routers/StackRouter.js index 5c9c2afc..7dd0500d 100644 --- a/packages/react-navigation/src/routers/StackRouter.js +++ b/packages/react-navigation/src/routers/StackRouter.js @@ -305,6 +305,12 @@ export default (routeConfigs, stackConfig = {}) => { // Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first. if (action.type === NavigationActions.POP_TO_TOP) { + // Refuse to handle pop to top if a key is given that doesn't correspond + // to this router + if (action.key && state.key !== action.key) { + return state; + } + // If we're already at the top, then we return the state with a new // identity so that the action is handled by this router. if (state.index === 0) { diff --git a/packages/react-navigation/src/routers/__tests__/StackRouter-test.js b/packages/react-navigation/src/routers/__tests__/StackRouter-test.js index 751f2256..5211a871 100644 --- a/packages/react-navigation/src/routers/__tests__/StackRouter-test.js +++ b/packages/react-navigation/src/routers/__tests__/StackRouter-test.js @@ -457,6 +457,35 @@ describe('StackRouter', () => { expect(state3 && state3.routes[1].index).toEqual(0); }); + test('popToTop targets StackRouter by key if specified', () => { + const ChildNavigator = () =>
; + ChildNavigator.router = StackRouter({ + Baz: { screen: () =>
}, + Qux: { screen: () =>
}, + }); + const router = StackRouter({ + Foo: { screen: () =>
}, + Bar: { screen: ChildNavigator }, + }); + const state = router.getStateForAction({ type: NavigationActions.INIT }); + const state2 = router.getStateForAction( + { + type: NavigationActions.NAVIGATE, + routeName: 'Bar', + }, + state + ); + const barKey = state2.routes[1].routes[0].key; + const state3 = router.getStateForAction( + { + type: NavigationActions.POP_TO_TOP, + key: state2.key, + }, + state2 + ); + expect(state3 && state3.index).toEqual(0); + }); + test('popToTop works as expected', () => { const TestRouter = StackRouter({ foo: { screen: () =>
}, diff --git a/packages/react-navigation/src/views/TabView/TabBarBottom.js b/packages/react-navigation/src/views/TabView/TabBarBottom.js index 8e390805..d8e02fe9 100644 --- a/packages/react-navigation/src/views/TabView/TabBarBottom.js +++ b/packages/react-navigation/src/views/TabView/TabBarBottom.js @@ -10,6 +10,7 @@ import { import SafeAreaView from 'react-native-safe-area-view'; import TabBarIcon from './TabBarIcon'; +import NavigationActions from '../../NavigationActions'; import withOrientation from '../withOrientation'; const majorVersion = parseInt(Platform.Version, 10); @@ -176,6 +177,24 @@ class TabBarBottom extends React.PureComponent { } } + _handleTabPress = index => { + const { jumpToIndex, navigation } = this.props; + const currentIndex = navigation.state.index; + + if (currentIndex === index) { + let childRoute = navigation.state.routes[index]; + if (childRoute.hasOwnProperty('index') && childRoute.index > 0) { + navigation.dispatch( + NavigationActions.popToTop({ key: childRoute.key }) + ); + } else { + // TODO: do something to scroll to top + } + } else { + jumpToIndex(index); + } + }; + render() { const { position, @@ -235,8 +254,13 @@ class TabBarBottom extends React.PureComponent { accessibilityLabel={accessibilityLabel} onPress={() => onPress - ? onPress({ previousScene, scene, jumpToIndex }) - : jumpToIndex(index) + ? onPress({ + previousScene, + scene, + jumpToIndex, + defaultHandler: this._handleTabPress, + }) + : this._handleTabPress(index) } > diff --git a/packages/react-navigation/src/views/TabView/TabBarTop.js b/packages/react-navigation/src/views/TabView/TabBarTop.js index 8eb4c2be..ab118c9a 100644 --- a/packages/react-navigation/src/views/TabView/TabBarTop.js +++ b/packages/react-navigation/src/views/TabView/TabBarTop.js @@ -91,7 +91,15 @@ export default class TabBarTop extends React.PureComponent { const onPress = getOnPress(previousScene, scene); if (onPress) { - onPress({ previousScene, scene, jumpToIndex }); + // To maintain the same API as `TabbarBottom`, we pass in a `defaultHandler` + // even though I don't believe in this case it should be any different + // than `jumpToIndex`. + onPress({ + previousScene, + scene, + jumpToIndex, + defaultHandler: jumpToIndex, + }); } else { jumpToIndex(scene.index); }