Skip to content

Commit 1de5494

Browse files
authored
feat: add target argument to setParams (facebook#18)
1 parent a64f402 commit 1de5494

15 files changed

+253
-87
lines changed

example/StackNavigator.tsx

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
CommonAction,
99
ParamListBase,
1010
Router,
11+
BaseRouter,
1112
createNavigator,
1213
} from '../src/index';
1314

@@ -28,9 +29,8 @@ type Action =
2829
| { type: 'POP_TO_TOP' };
2930

3031
export type StackNavigationProp<
31-
ParamList extends ParamListBase,
32-
RouteName extends keyof ParamList = string
33-
> = NavigationProp<ParamList, RouteName> & {
32+
ParamList extends ParamListBase
33+
> = NavigationProp<ParamList> & {
3434
/**
3535
* Push a new screen onto the stack.
3636
*
@@ -55,6 +55,7 @@ export type StackNavigationProp<
5555
};
5656

5757
const StackRouter: Router<CommonAction | Action> = {
58+
...BaseRouter,
5859
getInitialState({
5960
routeNames,
6061
initialRouteName = routeNames[0],
@@ -88,14 +89,6 @@ const StackRouter: Router<CommonAction | Action> = {
8889
return state;
8990
},
9091

91-
getStateForRouteNamesChange(state, { routeNames }) {
92-
return {
93-
...state,
94-
routeNames,
95-
routes: state.routes.filter(route => routeNames.includes(route.name)),
96-
};
97-
},
98-
9992
getStateForRouteFocus(state, key) {
10093
const index = state.routes.findIndex(r => r.key === key);
10194

@@ -246,18 +239,10 @@ const StackRouter: Router<CommonAction | Action> = {
246239
}
247240

248241
default:
249-
return null;
242+
return BaseRouter.getStateForAction(state, action);
250243
}
251244
},
252245

253-
shouldActionPropagateToChildren(action) {
254-
return action.type === 'NAVIGATE';
255-
},
256-
257-
shouldActionChangeFocus(action) {
258-
return action.type === 'NAVIGATE';
259-
},
260-
261246
actionCreators: {
262247
push(name: string, params?: object) {
263248
return { type: 'PUSH', payload: { name, params } };

example/TabNavigator.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Router,
1111
createNavigator,
1212
TargetRoute,
13+
BaseRouter,
1314
} from '../src/index';
1415

1516
type Props = {
@@ -22,10 +23,9 @@ type Action = {
2223
payload: { name?: string; key?: string; params?: object };
2324
};
2425

25-
export type TabNavigationProp<
26-
ParamList extends ParamListBase,
27-
RouteName extends keyof ParamList = string
28-
> = NavigationProp<ParamList, RouteName> & {
26+
export type TabNavigationProp<ParamList extends ParamListBase> = NavigationProp<
27+
ParamList
28+
> & {
2929
/**
3030
* Jump to an existing tab.
3131
*
@@ -168,7 +168,7 @@ const TabRouter: Router<Action | CommonAction> = {
168168
return null;
169169

170170
default:
171-
return null;
171+
return BaseRouter.getStateForAction(state, action);
172172
}
173173
},
174174

example/index.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
NavigationContainer,
55
CompositeNavigationProp,
66
PartialState,
7-
NavigationHelpers,
7+
NavigationProp,
88
RouteProp,
99
} from '../src';
1010
import StackNavigator, { StackNavigationProp } from './StackNavigator';
@@ -30,8 +30,8 @@ const First = ({
3030
route,
3131
}: {
3232
navigation: CompositeNavigationProp<
33-
StackNavigationProp<StackParamList, 'first'>,
34-
NavigationHelpers<TabParamList>
33+
StackNavigationProp<StackParamList>,
34+
NavigationProp<TabParamList>
3535
>;
3636
route: RouteProp<StackParamList, 'first'>;
3737
}) => (
@@ -62,8 +62,8 @@ const Second = ({
6262
navigation,
6363
}: {
6464
navigation: CompositeNavigationProp<
65-
StackNavigationProp<StackParamList, 'second'>,
66-
NavigationHelpers<TabParamList>
65+
StackNavigationProp<StackParamList>,
66+
NavigationProp<TabParamList>
6767
>;
6868
}) => (
6969
<div>
@@ -84,7 +84,7 @@ const Fourth = ({
8484
navigation,
8585
}: {
8686
navigation: CompositeNavigationProp<
87-
TabNavigationProp<TabParamList, 'fourth'>,
87+
TabNavigationProp<TabParamList>,
8888
StackNavigationProp<StackParamList>
8989
>;
9090
}) => (
@@ -109,7 +109,7 @@ const Fifth = ({
109109
navigation,
110110
}: {
111111
navigation: CompositeNavigationProp<
112-
TabNavigationProp<TabParamList, 'fifth'>,
112+
TabNavigationProp<TabParamList>,
113113
StackNavigationProp<StackParamList>
114114
>;
115115
}) => (

src/BaseActions.tsx

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,28 @@ export type Action =
1313
| {
1414
type: 'RESET';
1515
payload: PartialState & { key?: string };
16+
}
17+
| {
18+
type: 'SET_PARAMS';
19+
payload: { name?: string; key?: string; params?: object };
1620
};
1721

1822
export function goBack(): Action {
1923
return { type: 'GO_BACK' };
2024
}
2125

2226
export function navigate(target: TargetRoute<string>, params?: object): Action {
23-
if (
24-
(target.hasOwnProperty('key') && target.hasOwnProperty('name')) ||
25-
(!target.hasOwnProperty('key') && !target.hasOwnProperty('name'))
26-
) {
27-
throw new Error(
28-
'While calling navigate you need to specify either name or key'
29-
);
30-
}
3127
if (typeof target === 'string') {
3228
return { type: 'NAVIGATE', payload: { name: target, params } };
3329
} else {
30+
if (
31+
(target.hasOwnProperty('key') && target.hasOwnProperty('name')) ||
32+
(!target.hasOwnProperty('key') && !target.hasOwnProperty('name'))
33+
) {
34+
throw new Error(
35+
'While calling navigate you need to specify either name or key'
36+
);
37+
}
3438
return { type: 'NAVIGATE', payload: { ...target, params } };
3539
}
3640
}
@@ -42,3 +46,20 @@ export function replace(name: string, params?: object): Action {
4246
export function reset(state: PartialState & { key?: string }): Action {
4347
return { type: 'RESET', payload: state };
4448
}
49+
50+
export function setParams(
51+
params: object,
52+
target: { name?: string; key?: string }
53+
): Action {
54+
if (
55+
target &&
56+
((target.hasOwnProperty('key') && target.hasOwnProperty('name')) ||
57+
(!target.hasOwnProperty('key') && !target.hasOwnProperty('name')))
58+
) {
59+
throw new Error(
60+
'While calling setState with given second param you need to specify either name or key'
61+
);
62+
}
63+
64+
return { type: 'SET_PARAMS', payload: { params, ...target } };
65+
}

src/BaseRouter.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { CommonAction, Router } from './types';
2+
3+
const BaseRouter: Omit<
4+
Omit<Router<CommonAction>, 'getInitialState'>,
5+
'getRehydratedState'
6+
> = {
7+
getStateForAction(state, action) {
8+
switch (action.type) {
9+
case 'SET_PARAMS':
10+
return {
11+
...state,
12+
routes: state.routes.map(r =>
13+
r.key === action.payload.key || r.name === action.payload.name
14+
? { ...r, params: { ...r.params, ...action.payload.params } }
15+
: r
16+
),
17+
};
18+
default:
19+
return null;
20+
}
21+
},
22+
23+
getStateForRouteNamesChange(state, { routeNames }) {
24+
return {
25+
...state,
26+
routeNames,
27+
routes: state.routes.filter(route => routeNames.includes(route.name)),
28+
};
29+
},
30+
31+
getStateForRouteFocus(state, key) {
32+
const index = state.routes.findIndex(r => r.key === key);
33+
34+
if (index === -1 || index === state.index) {
35+
return state;
36+
}
37+
38+
return { ...state, index };
39+
},
40+
shouldActionPropagateToChildren(action) {
41+
return action.type === 'NAVIGATE';
42+
},
43+
44+
shouldActionChangeFocus(action) {
45+
return action.type === 'NAVIGATE';
46+
},
47+
};
48+
49+
export default BaseRouter;

src/NavigationBuilderContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import * as React from 'react';
2-
import { NavigationHelpers, NavigationAction } from './types';
2+
import { NavigationProp, NavigationAction } from './types';
33

44
export type ChildActionListener = (
55
action: NavigationAction,
66
sourceRouteKey?: string
77
) => boolean;
88

99
const NavigationBuilderContext = React.createContext<{
10-
navigation?: NavigationHelpers;
10+
navigation?: NavigationProp;
1111
onAction?: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
1212
addActionListener?: (listener: ChildActionListener) => void;
1313
removeActionListener?: (listener: ChildActionListener) => void;

src/SceneView.tsx

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import StaticContainer from './StaticContainer';
44
import {
55
Route,
66
NavigationState,
7-
NavigationHelpers,
7+
NavigationProp,
88
RouteConfig,
9+
TargetRoute,
910
} from './types';
1011
import EnsureSingleNavigator from './EnsureSingleNavigator';
1112

1213
type Props = {
1314
screen: RouteConfig;
14-
navigation: NavigationHelpers;
15+
navigation: NavigationProp;
1516
route: Route & { state?: NavigationState };
1617
getState: () => NavigationState;
1718
setState: (state: NavigationState) => void;
@@ -24,22 +25,11 @@ export default function SceneView(props: Props) {
2425
const navigation = React.useMemo(
2526
() => ({
2627
...helpers,
27-
setParams: (params: object) => {
28-
performTransaction(() => {
29-
const state = getState();
30-
31-
setState({
32-
...state,
33-
routes: state.routes.map(r =>
34-
r.key === route.key
35-
? { ...r, params: { ...r.params, ...params } }
36-
: r
37-
),
38-
});
39-
});
28+
setParams: (params: object, target?: TargetRoute<string>) => {
29+
helpers.setParams(params, target ? target : { key: route.key });
4030
},
4131
}),
42-
[getState, helpers, performTransaction, route.key, setState]
32+
[helpers, route.key]
4333
);
4434

4535
const getCurrentState = React.useCallback(() => {

src/__tests__/BaseActions.test.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,73 @@ it('throws if NAVIGATE dispatched neither both key nor name', () => {
7676
'While calling navigate you need to specify either name or key'
7777
);
7878
});
79+
80+
it('throws if SET_PARAMS dispatched with both key and name', () => {
81+
const TestNavigator = (props: any) => {
82+
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
83+
84+
return descriptors[state.routes[state.index].key].render();
85+
};
86+
87+
const FooScreen = (props: any) => {
88+
React.useEffect(() => {
89+
props.navigation.setParams({}, { key: '1', name: '2' });
90+
// eslint-disable-next-line react-hooks/exhaustive-deps
91+
}, []);
92+
93+
return null;
94+
};
95+
96+
const onStateChange = jest.fn();
97+
98+
const element = (
99+
<NavigationContainer onStateChange={onStateChange}>
100+
<TestNavigator initialRouteName="foo">
101+
<Screen
102+
name="foo"
103+
component={FooScreen}
104+
initialParams={{ count: 10 }}
105+
/>
106+
</TestNavigator>
107+
</NavigationContainer>
108+
);
109+
110+
expect(() => render(element).update(element)).toThrowError(
111+
'While calling setState with given second param you need to specify either name or key'
112+
);
113+
});
114+
115+
it('throws if SET_PARAMS dispatched neither both key nor name', () => {
116+
const TestNavigator = (props: any) => {
117+
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
118+
119+
return descriptors[state.routes[state.index].key].render();
120+
};
121+
122+
const FooScreen = (props: any) => {
123+
React.useEffect(() => {
124+
props.navigation.setParams({}, {});
125+
// eslint-disable-next-line react-hooks/exhaustive-deps
126+
}, []);
127+
128+
return null;
129+
};
130+
131+
const onStateChange = jest.fn();
132+
133+
const element = (
134+
<NavigationContainer onStateChange={onStateChange}>
135+
<TestNavigator initialRouteName="foo">
136+
<Screen
137+
name="foo"
138+
component={FooScreen}
139+
initialParams={{ count: 10 }}
140+
/>
141+
</TestNavigator>
142+
</NavigationContainer>
143+
);
144+
145+
expect(() => render(element).update(element)).toThrowError(
146+
'While calling setState with given second param you need to specify either name or key'
147+
);
148+
});

0 commit comments

Comments
 (0)