Fix stack router child router delegation priority (#4635)

Stack router had some aggressive logic for deferring to inactive child routers. The child router behavior should come after all of the appropriate stack actions, with the exception of the active child router.

This was causing issues such as https://github.com/react-navigation/react-navigation/issues/4623 , where inactive tab navigators would handle the back action, and cause the stack to attempt to  pop back to it.
This commit is contained in:
Eric Vicenti 2018-07-03 21:18:25 -07:00 committed by Brent Vatne
parent 0921889f7a
commit e8c1833053
2 changed files with 90 additions and 27 deletions

View File

@ -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;
},

View File

@ -1002,6 +1002,43 @@ describe('StackRouter', () => {
expect(state3 && state3.isTransitioning).toEqual(false);
});
test('Back action parent is prioritized over inactive child routers', () => {
const Bar = () => <div />;
Bar.router = StackRouter({
baz: { screen: () => <div /> },
qux: { screen: () => <div /> },
});
const TestRouter = StackRouter({
foo: { screen: () => <div /> },
bar: { screen: Bar },
boo: { screen: () => <div /> },
});
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 = () => <div />;
const BarScreen = () => <div />;