Skip to content

Commit

Permalink
feat: add target argument to setParams (facebook#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
osdnk authored Jul 20, 2019
1 parent a64f402 commit 1de5494
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 87 deletions.
25 changes: 5 additions & 20 deletions example/StackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CommonAction,
ParamListBase,
Router,
BaseRouter,
createNavigator,
} from '../src/index';

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

export type StackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<ParamList, RouteName> & {
ParamList extends ParamListBase
> = NavigationProp<ParamList> & {
/**
* Push a new screen onto the stack.
*
Expand All @@ -55,6 +55,7 @@ export type StackNavigationProp<
};

const StackRouter: Router<CommonAction | Action> = {
...BaseRouter,
getInitialState({
routeNames,
initialRouteName = routeNames[0],
Expand Down Expand Up @@ -88,14 +89,6 @@ const StackRouter: Router<CommonAction | Action> = {
return state;
},

getStateForRouteNamesChange(state, { routeNames }) {
return {
...state,
routeNames,
routes: state.routes.filter(route => routeNames.includes(route.name)),
};
},

getStateForRouteFocus(state, key) {
const index = state.routes.findIndex(r => r.key === key);

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

default:
return null;
return BaseRouter.getStateForAction(state, action);
}
},

shouldActionPropagateToChildren(action) {
return action.type === 'NAVIGATE';
},

shouldActionChangeFocus(action) {
return action.type === 'NAVIGATE';
},

actionCreators: {
push(name: string, params?: object) {
return { type: 'PUSH', payload: { name, params } };
Expand Down
10 changes: 5 additions & 5 deletions example/TabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Router,
createNavigator,
TargetRoute,
BaseRouter,
} from '../src/index';

type Props = {
Expand All @@ -22,10 +23,9 @@ type Action = {
payload: { name?: string; key?: string; params?: object };
};

export type TabNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<ParamList, RouteName> & {
export type TabNavigationProp<ParamList extends ParamListBase> = NavigationProp<
ParamList
> & {
/**
* Jump to an existing tab.
*
Expand Down Expand Up @@ -168,7 +168,7 @@ const TabRouter: Router<Action | CommonAction> = {
return null;

default:
return null;
return BaseRouter.getStateForAction(state, action);
}
},

Expand Down
14 changes: 7 additions & 7 deletions example/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
NavigationContainer,
CompositeNavigationProp,
PartialState,
NavigationHelpers,
NavigationProp,
RouteProp,
} from '../src';
import StackNavigator, { StackNavigationProp } from './StackNavigator';
Expand All @@ -30,8 +30,8 @@ const First = ({
route,
}: {
navigation: CompositeNavigationProp<
StackNavigationProp<StackParamList, 'first'>,
NavigationHelpers<TabParamList>
StackNavigationProp<StackParamList>,
NavigationProp<TabParamList>
>;
route: RouteProp<StackParamList, 'first'>;
}) => (
Expand Down Expand Up @@ -62,8 +62,8 @@ const Second = ({
navigation,
}: {
navigation: CompositeNavigationProp<
StackNavigationProp<StackParamList, 'second'>,
NavigationHelpers<TabParamList>
StackNavigationProp<StackParamList>,
NavigationProp<TabParamList>
>;
}) => (
<div>
Expand All @@ -84,7 +84,7 @@ const Fourth = ({
navigation,
}: {
navigation: CompositeNavigationProp<
TabNavigationProp<TabParamList, 'fourth'>,
TabNavigationProp<TabParamList>,
StackNavigationProp<StackParamList>
>;
}) => (
Expand All @@ -109,7 +109,7 @@ const Fifth = ({
navigation,
}: {
navigation: CompositeNavigationProp<
TabNavigationProp<TabParamList, 'fifth'>,
TabNavigationProp<TabParamList>,
StackNavigationProp<StackParamList>
>;
}) => (
Expand Down
37 changes: 29 additions & 8 deletions src/BaseActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,28 @@ export type Action =
| {
type: 'RESET';
payload: PartialState & { key?: string };
}
| {
type: 'SET_PARAMS';
payload: { name?: string; key?: string; params?: object };
};

export function goBack(): Action {
return { type: 'GO_BACK' };
}

export function navigate(target: TargetRoute<string>, params?: object): Action {
if (
(target.hasOwnProperty('key') && target.hasOwnProperty('name')) ||
(!target.hasOwnProperty('key') && !target.hasOwnProperty('name'))
) {
throw new Error(
'While calling navigate you need to specify either name or key'
);
}
if (typeof target === 'string') {
return { type: 'NAVIGATE', payload: { name: target, params } };
} else {
if (
(target.hasOwnProperty('key') && target.hasOwnProperty('name')) ||
(!target.hasOwnProperty('key') && !target.hasOwnProperty('name'))
) {
throw new Error(
'While calling navigate you need to specify either name or key'
);
}
return { type: 'NAVIGATE', payload: { ...target, params } };
}
}
Expand All @@ -42,3 +46,20 @@ export function replace(name: string, params?: object): Action {
export function reset(state: PartialState & { key?: string }): Action {
return { type: 'RESET', payload: state };
}

export function setParams(
params: object,
target: { name?: string; key?: string }
): Action {
if (
target &&
((target.hasOwnProperty('key') && target.hasOwnProperty('name')) ||
(!target.hasOwnProperty('key') && !target.hasOwnProperty('name')))
) {
throw new Error(
'While calling setState with given second param you need to specify either name or key'
);
}

return { type: 'SET_PARAMS', payload: { params, ...target } };
}
49 changes: 49 additions & 0 deletions src/BaseRouter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CommonAction, Router } from './types';

const BaseRouter: Omit<
Omit<Router<CommonAction>, 'getInitialState'>,
'getRehydratedState'
> = {
getStateForAction(state, action) {
switch (action.type) {
case 'SET_PARAMS':
return {
...state,
routes: state.routes.map(r =>
r.key === action.payload.key || r.name === action.payload.name
? { ...r, params: { ...r.params, ...action.payload.params } }
: r
),
};
default:
return null;
}
},

getStateForRouteNamesChange(state, { routeNames }) {
return {
...state,
routeNames,
routes: state.routes.filter(route => routeNames.includes(route.name)),
};
},

getStateForRouteFocus(state, key) {
const index = state.routes.findIndex(r => r.key === key);

if (index === -1 || index === state.index) {
return state;
}

return { ...state, index };
},
shouldActionPropagateToChildren(action) {
return action.type === 'NAVIGATE';
},

shouldActionChangeFocus(action) {
return action.type === 'NAVIGATE';
},
};

export default BaseRouter;
4 changes: 2 additions & 2 deletions src/NavigationBuilderContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as React from 'react';
import { NavigationHelpers, NavigationAction } from './types';
import { NavigationProp, NavigationAction } from './types';

export type ChildActionListener = (
action: NavigationAction,
sourceRouteKey?: string
) => boolean;

const NavigationBuilderContext = React.createContext<{
navigation?: NavigationHelpers;
navigation?: NavigationProp;
onAction?: (action: NavigationAction, sourceNavigatorKey?: string) => boolean;
addActionListener?: (listener: ChildActionListener) => void;
removeActionListener?: (listener: ChildActionListener) => void;
Expand Down
22 changes: 6 additions & 16 deletions src/SceneView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import StaticContainer from './StaticContainer';
import {
Route,
NavigationState,
NavigationHelpers,
NavigationProp,
RouteConfig,
TargetRoute,
} from './types';
import EnsureSingleNavigator from './EnsureSingleNavigator';

type Props = {
screen: RouteConfig;
navigation: NavigationHelpers;
navigation: NavigationProp;
route: Route & { state?: NavigationState };
getState: () => NavigationState;
setState: (state: NavigationState) => void;
Expand All @@ -24,22 +25,11 @@ export default function SceneView(props: Props) {
const navigation = React.useMemo(
() => ({
...helpers,
setParams: (params: object) => {
performTransaction(() => {
const state = getState();

setState({
...state,
routes: state.routes.map(r =>
r.key === route.key
? { ...r, params: { ...r.params, ...params } }
: r
),
});
});
setParams: (params: object, target?: TargetRoute<string>) => {
helpers.setParams(params, target ? target : { key: route.key });
},
}),
[getState, helpers, performTransaction, route.key, setState]
[helpers, route.key]
);

const getCurrentState = React.useCallback(() => {
Expand Down
70 changes: 70 additions & 0 deletions src/__tests__/BaseActions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,73 @@ it('throws if NAVIGATE dispatched neither both key nor name', () => {
'While calling navigate you need to specify either name or key'
);
});

it('throws if SET_PARAMS dispatched with both key and name', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);

return descriptors[state.routes[state.index].key].render();
};

const FooScreen = (props: any) => {
React.useEffect(() => {
props.navigation.setParams({}, { key: '1', name: '2' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return null;
};

const onStateChange = jest.fn();

const element = (
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
component={FooScreen}
initialParams={{ count: 10 }}
/>
</TestNavigator>
</NavigationContainer>
);

expect(() => render(element).update(element)).toThrowError(
'While calling setState with given second param you need to specify either name or key'
);
});

it('throws if SET_PARAMS dispatched neither both key nor name', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);

return descriptors[state.routes[state.index].key].render();
};

const FooScreen = (props: any) => {
React.useEffect(() => {
props.navigation.setParams({}, {});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return null;
};

const onStateChange = jest.fn();

const element = (
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
component={FooScreen}
initialParams={{ count: 10 }}
/>
</TestNavigator>
</NavigationContainer>
);

expect(() => render(element).update(element)).toThrowError(
'While calling setState with given second param you need to specify either name or key'
);
});
Loading

0 comments on commit 1de5494

Please sign in to comment.