diff --git a/packages/react-navigation/src/routers/StackRouter.js b/packages/react-navigation/src/routers/StackRouter.js index 0720bd21..93fac47a 100644 --- a/packages/react-navigation/src/routers/StackRouter.js +++ b/packages/react-navigation/src/routers/StackRouter.js @@ -193,37 +193,27 @@ export default (routeConfigs, stackConfig = {}) => { return getInitialState(action); } + const activeChildRoute = state.routes[state.index]; + if ( !isResetToRootStack(action) && action.type !== NavigationActions.NAVIGATE ) { - const keyIndex = action.key - ? StateUtils.indexOf(state, action.key) - : -1; - - // Traverse routes from the top of the stack to the bottom, so the - // active route has the first opportunity, then the one before it, etc. - for (let childRoute of state.routes.slice().reverse()) { - // If a key is provided and in routes state then let's use that - // knowledge to skip extra getStateForAction calls on other child - // routers - if (keyIndex >= 0 && childRoute.key !== action.key) { - continue; - } - let childRouter = childRouters[childRoute.routeName]; - if (childRouter) { - const route = childRouter.getStateForAction(action, childRoute); - - if (route === null) { - return state; - } else if (route && route !== childRoute) { - return StateUtils.replaceAt( - state, - childRoute.key, - route, - action.type === NavigationActions.SET_PARAMS - ); - } + // Let the active child router handle the action + const activeChildRouter = childRouters[activeChildRoute.routeName]; + if (activeChildRouter) { + const route = activeChildRouter.getStateForAction( + action, + activeChildRoute + ); + if (route !== null && route !== activeChildRoute) { + return StateUtils.replaceAt( + state, + activeChildRoute.key, + route, + // the following tells replaceAt to NOT change the index to this route for the setParam action, because people don't expect param-setting actions to switch the active route + action.type === NavigationActions.SET_PARAMS + ); } } } else if (action.type === NavigationActions.NAVIGATE) { @@ -522,6 +512,42 @@ export default (routeConfigs, stackConfig = {}) => { } } + // By this point in the router's state handling logic, we have handled the behavior of the active route, and handled any stack actions. + // If we haven't returned by now, we should allow non-active child routers to handle this action, and switch to that index if the child state (route) does change.. + + const keyIndex = action.key ? StateUtils.indexOf(state, action.key) : -1; + + // Traverse routes from the top of the stack to the bottom, so the + // active route has the first opportunity, then the one before it, etc. + for (let childRoute of state.routes.slice().reverse()) { + if (childRoute.key === activeChildRoute.key) { + // skip over the active child because we let it attempt to handle the action earlier + continue; + } + // If a key is provided and in routes state then let's use that + // knowledge to skip extra getStateForAction calls on other child + // routers + if (keyIndex >= 0 && childRoute.key !== action.key) { + continue; + } + let childRouter = childRouters[childRoute.routeName]; + if (childRouter) { + const route = childRouter.getStateForAction(action, childRoute); + + if (route === null) { + return state; + } else if (route && route !== childRoute) { + return StateUtils.replaceAt( + state, + childRoute.key, + route, + // the following tells replaceAt to NOT change the index to this route for the setParam action, because people don't expect param-setting actions to switch the active route + action.type === NavigationActions.SET_PARAMS + ); + } + } + } + return state; }, diff --git a/packages/react-navigation/src/routers/__tests__/StackRouter-test.js b/packages/react-navigation/src/routers/__tests__/StackRouter-test.js index e5b9d31c..03d1c053 100644 --- a/packages/react-navigation/src/routers/__tests__/StackRouter-test.js +++ b/packages/react-navigation/src/routers/__tests__/StackRouter-test.js @@ -1002,6 +1002,43 @@ describe('StackRouter', () => { expect(state3 && state3.isTransitioning).toEqual(false); }); + test('Back action parent is prioritized over inactive child routers', () => { + const Bar = () =>
; + Bar.router = StackRouter({ + baz: { screen: () =>
}, + qux: { screen: () =>
}, + }); + const TestRouter = StackRouter({ + foo: { screen: () =>
}, + bar: { screen: Bar }, + boo: { screen: () =>
}, + }); + const state = { + key: 'top', + index: 3, + routes: [ + { routeName: 'foo', key: 'f' }, + { + routeName: 'bar', + key: 'b', + index: 1, + routes: [ + { routeName: 'baz', key: 'bz' }, + { routeName: 'qux', key: 'bx' }, + ], + }, + { routeName: 'foo', key: 'f1' }, + { routeName: 'boo', key: 'z' }, + ], + }; + const testState = TestRouter.getStateForAction( + { type: NavigationActions.BACK }, + state + ); + expect(testState.index).toBe(2); + expect(testState.routes[1].index).toBe(1); + }); + test('Handle basic stack logic for components with router', () => { const FooScreen = () =>
; const BarScreen = () =>
;