Skip to content

Commit

Permalink
Pop to top on TabNavigator when tapping already focused tab icon (#3589)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
brentvatne committed Feb 26, 2018
1 parent 276fb8f commit c8531d0
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/NavigationActions.js
Expand Up @@ -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 => {
Expand Down
6 changes: 6 additions & 0 deletions src/routers/StackRouter.js
Expand Up @@ -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) {
Expand Down
29 changes: 29 additions & 0 deletions src/routers/__tests__/StackRouter-test.js
Expand Up @@ -457,6 +457,35 @@ describe('StackRouter', () => {
expect(state3 && state3.routes[1].index).toEqual(0);
});

test('popToTop targets StackRouter by key if specified', () => {
const ChildNavigator = () => <div />;
ChildNavigator.router = StackRouter({
Baz: { screen: () => <div /> },
Qux: { screen: () => <div /> },
});
const router = StackRouter({
Foo: { screen: () => <div /> },
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: () => <div /> },
Expand Down
28 changes: 26 additions & 2 deletions src/views/TabView/TabBarBottom.js
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
>
<Animated.View style={[styles.tab, { backgroundColor }]}>
Expand Down
10 changes: 9 additions & 1 deletion src/views/TabView/TabBarTop.js
Expand Up @@ -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);
}
Expand Down

0 comments on commit c8531d0

Please sign in to comment.