Skip to content
This repository was archived by the owner on Feb 25, 2020. It is now read-only.

Commit 304b437

Browse files
authored
fix: fixed Tab/SwitchRouter incorrectly switching children on "ba… (#74)
1 parent 38c7d18 commit 304b437

File tree

3 files changed

+163
-36
lines changed

3 files changed

+163
-36
lines changed

src/routers/SwitchRouter.js

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import createConfigGetter from './createConfigGetter';
44

55
import * as NavigationActions from '../NavigationActions';
66
import * as SwitchActions from './SwitchActions';
7+
import * as StackActions from './StackActions';
78
import validateRouteConfigMap from './validateRouteConfigMap';
89
import { createPathParser } from './pathUtils';
910

@@ -248,8 +249,6 @@ export default (routeConfigs, config = {}) => {
248249
const routeKey =
249250
state.routeKeyHistory[state.routeKeyHistory.length - 2];
250251
activeChildIndex = order.indexOf(routeKey);
251-
} else {
252-
return state;
253252
}
254253
}
255254

@@ -337,44 +336,57 @@ export default (routeConfigs, config = {}) => {
337336
return { ...state };
338337
}
339338

339+
const isActionBackOrPop =
340+
action.type === NavigationActions.BACK ||
341+
action.type === StackActions.POP ||
342+
action.type === StackActions.POP_TO_TOP;
343+
const sendActionToInactiveChildren =
344+
!isActionBackOrPop ||
345+
(action.type === NavigationActions.BACK && action.key != null);
346+
340347
// Let other children handle it and switch to the first child that returns a new state
341-
let index = state.index;
342-
let routes = state.routes;
343-
order.find((childId, i) => {
344-
const childRouter = childRouters[childId];
345-
if (i === index) {
348+
// Do not do this for StackActions.POP or NavigationActions.BACK actions without a key:
349+
// it would be unintuitive for these actions to switch to another tab just because that tab had a Stack that could accept a back action
350+
if (sendActionToInactiveChildren) {
351+
let index = state.index;
352+
let routes = state.routes;
353+
order.find((childId, i) => {
354+
const childRouter = childRouters[childId];
355+
if (i === index) {
356+
return false;
357+
}
358+
let childState = routes[i];
359+
if (childRouter) {
360+
childState = childRouter.getStateForAction(action, childState);
361+
}
362+
if (!childState) {
363+
index = i;
364+
return true;
365+
}
366+
if (childState !== routes[i]) {
367+
routes = [...routes];
368+
routes[i] = childState;
369+
index = i;
370+
return true;
371+
}
346372
return false;
373+
});
374+
375+
// Nested routers can be updated after switching children with actions such as SET_PARAMS
376+
// and COMPLETE_TRANSITION.
377+
if (action.preserveFocus) {
378+
index = state.index;
347379
}
348-
let childState = routes[i];
349-
if (childRouter) {
350-
childState = childRouter.getStateForAction(action, childState);
351-
}
352-
if (!childState) {
353-
index = i;
354-
return true;
355-
}
356-
if (childState !== routes[i]) {
357-
routes = [...routes];
358-
routes[i] = childState;
359-
index = i;
360-
return true;
361-
}
362-
return false;
363-
});
364380

365-
// Nested routers can be updated after switching children with actions such as SET_PARAMS
366-
// and COMPLETE_TRANSITION.
367-
if (action.preserveFocus) {
368-
index = state.index;
381+
if (index !== state.index || routes !== state.routes) {
382+
return getNextState(action, prevState, {
383+
...state,
384+
index,
385+
routes,
386+
});
387+
}
369388
}
370389

371-
if (index !== state.index || routes !== state.routes) {
372-
return getNextState(action, prevState, {
373-
...state,
374-
index,
375-
routes,
376-
});
377-
}
378390
return state;
379391
},
380392

src/routers/__tests__/SwitchRouter-test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,100 @@ describe('SwitchRouter', () => {
139139
expect(getSubState(2).routeName).toEqual('B1');
140140
});
141141

142+
it('handles back and does not apply back action to inactive child', () => {
143+
const { navigateTo, back, getSubState } = getRouterTestHelper(
144+
getExampleRouter({
145+
backBehavior: 'initialRoute',
146+
resetOnBlur: false, // Don't erase the state of substack B when we switch back to A
147+
})
148+
);
149+
150+
expect(getSubState(1).routeName).toEqual('A');
151+
152+
navigateTo('B');
153+
navigateTo('B2');
154+
expect(getSubState(1).routeName).toEqual('B');
155+
expect(getSubState(2).routeName).toEqual('B2');
156+
157+
navigateTo('A');
158+
expect(getSubState(1).routeName).toEqual('A');
159+
160+
// The back action should not switch to B. It should stay on A
161+
back({ key: null });
162+
expect(getSubState(1).routeName).toEqual('A');
163+
});
164+
165+
it('handles pop and does not apply pop action to inactive child', () => {
166+
const { navigateTo, pop, getSubState } = getRouterTestHelper(
167+
getExampleRouter({
168+
backBehavior: 'initialRoute',
169+
resetOnBlur: false, // Don't erase the state of substack B when we switch back to A
170+
})
171+
);
172+
173+
expect(getSubState(1).routeName).toEqual('A');
174+
175+
navigateTo('B');
176+
navigateTo('B2');
177+
expect(getSubState(1).routeName).toEqual('B');
178+
expect(getSubState(2).routeName).toEqual('B2');
179+
180+
navigateTo('A');
181+
expect(getSubState(1).routeName).toEqual('A');
182+
183+
// The pop action should not switch to B. It should stay on A
184+
pop();
185+
expect(getSubState(1).routeName).toEqual('A');
186+
});
187+
188+
it('handles popToTop and does not apply popToTop action to inactive child', () => {
189+
const { navigateTo, popToTop, getSubState } = getRouterTestHelper(
190+
getExampleRouter({
191+
backBehavior: 'initialRoute',
192+
resetOnBlur: false, // Don't erase the state of substack B when we switch back to A
193+
})
194+
);
195+
196+
expect(getSubState(1).routeName).toEqual('A');
197+
198+
navigateTo('B');
199+
navigateTo('B2');
200+
expect(getSubState(1).routeName).toEqual('B');
201+
expect(getSubState(2).routeName).toEqual('B2');
202+
203+
navigateTo('A');
204+
expect(getSubState(1).routeName).toEqual('A');
205+
206+
// The popToTop action should not switch to B. It should stay on A
207+
popToTop();
208+
expect(getSubState(1).routeName).toEqual('A');
209+
});
210+
211+
it('handles back and does switch to inactive child with matching key', () => {
212+
const { navigateTo, back, getSubState } = getRouterTestHelper(
213+
getExampleRouter({
214+
backBehavior: 'initialRoute',
215+
resetOnBlur: false, // Don't erase the state of substack B when we switch back to A
216+
})
217+
);
218+
219+
expect(getSubState(1).routeName).toEqual('A');
220+
221+
navigateTo('B');
222+
navigateTo('B2');
223+
expect(getSubState(1).routeName).toEqual('B');
224+
expect(getSubState(2).routeName).toEqual('B2');
225+
const b2Key = getSubState(2).key;
226+
227+
navigateTo('A');
228+
expect(getSubState(1).routeName).toEqual('A');
229+
230+
// The back action should switch to B and go back from B2 to B1
231+
back(b2Key);
232+
expect(getSubState(1).routeName).toEqual('B');
233+
expect(getSubState(2).routeName).toEqual('B1');
234+
});
235+
142236
it('handles nested actions', () => {
143237
const { navigateTo, getSubState } = getRouterTestHelper(getExampleRouter());
144238

src/routers/__tests__/routerTestHelper.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as NavigationActions from '../../NavigationActions';
22
import * as SwitchActions from '../../routers/SwitchActions';
3+
import * as StackActions from '../../routers/StackActions';
34

45
// A simple helper that makes it easier to write basic routing tests
56
// We generally want to apply one action after the other and check router returns correct state
@@ -34,9 +35,20 @@ export const getRouterTestHelper = (router, options = defaultOptions) => {
3435
...otherActionAttributes,
3536
});
3637

37-
const back = () =>
38+
const back = key =>
3839
applyAction({
3940
type: NavigationActions.BACK,
41+
key,
42+
});
43+
44+
const pop = () =>
45+
applyAction({
46+
type: StackActions.POP,
47+
});
48+
49+
const popToTop = () =>
50+
applyAction({
51+
type: StackActions.POP_TO_TOP,
4052
});
4153

4254
const getState = () => state;
@@ -45,7 +57,16 @@ export const getRouterTestHelper = (router, options = defaultOptions) => {
4557
return getSubStateRecursive(state, level);
4658
};
4759

48-
return { applyAction, navigateTo, jumpTo, back, getState, getSubState };
60+
return {
61+
applyAction,
62+
navigateTo,
63+
jumpTo,
64+
back,
65+
pop,
66+
popToTop,
67+
getState,
68+
getSubState,
69+
};
4970
};
5071

5172
const getSubStateRecursive = (state, level = 1) => {

0 commit comments

Comments
 (0)