Skip to content

Commit

Permalink
feat: handle route names change
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Jul 18, 2019
1 parent 95773de commit b775dba
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 6 deletions.
8 changes: 8 additions & 0 deletions example/StackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ const StackRouter: Router<CommonAction | Action> = {
return state;
},

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

getStateForAction(state, action) {
switch (action.type) {
case 'PUSH':
Expand Down
17 changes: 16 additions & 1 deletion example/TabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@ const TabRouter: Router<Action | CommonAction> = {
return state;
},

getStateForRouteNamesChange(state, { routeNames, initialParamsList }) {
return {
...state,
routeNames,
routes: routeNames.map(
name =>
state.routes.find(r => r.name === name) || {
name,
key: `${name}-${shortid()}`,
params: initialParamsList[name],
}
),
};
},

getStateForAction(state, action) {
switch (action.type) {
case 'JUMP_TO':
Expand Down Expand Up @@ -190,4 +205,4 @@ export function TabNavigator(props: Props) {
);
}

export default createNavigator(TabNavigator);
export default createNavigator(TabNavigator);
43 changes: 43 additions & 0 deletions src/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export const MockRouter: Router<{ type: string }> & { key: number } = {
return state;
},

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

getStateForAction(state, action) {
switch (action.type) {
case 'UPDATE':
Expand Down Expand Up @@ -535,6 +543,41 @@ it('updates route params with setParams', () => {
});
});

it('handles change in route names', () => {
const TestNavigator = (props: any): any => {
useNavigationBuilder(MockRouter, props);
return null;
};

const onStateChange = jest.fn();

const root = render(
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="bar">
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);

root.update(
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo" component={jest.fn()} />
<Screen name="baz" component={jest.fn()} />
<Screen name="qux" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);

expect(onStateChange).toBeCalledWith({
index: 1,
key: '0',
routeNames: ['foo', 'baz', 'qux'],
routes: [{ key: 'foo', name: 'foo' }],
});
});

it('throws if navigator is not inside a container', () => {
const TestNavigator = (props: any) => {
useNavigationBuilder(MockRouter, props);
Expand Down
27 changes: 22 additions & 5 deletions src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ export type Router<Action extends NavigationAction = CommonAction> = {
/**
* Initialize the navigation state.
*
* @param routeNames List of valid route names as defined in the screen components.
* @param initialRouteName Route to focus in the state.
* @param initialParamsList Object containing initial params for each route.
* @param options.routeNames List of valid route names as defined in the screen components.
* @param options.initialRouteName Route to focus in the state.
* @param options.initialParamsList Object containing initial params for each route.
*/
getInitialState(options: {
routeNames: string[];
Expand All @@ -67,14 +67,31 @@ export type Router<Action extends NavigationAction = CommonAction> = {
/**
* Rehydrate the full navigation state from a given partial state.
*
* @param routeNames List of valid route names as defined in the screen components.
* @param partialState Navigation state to rehydrate from.
* @param options.routeNames List of valid route names as defined in the screen components.
* @param options.partialState Navigation state to rehydrate from.
*/
getRehydratedState(options: {
routeNames: string[];
partialState: NavigationState | PartialState;
}): NavigationState;

/**
* Take the current state and updated list of route names, and return a new state.
*
* @param state State object to update.
* @param options.routeNames New list of route names.
* @param options.initialRouteName Route to focus in the state.
* @param options.initialParamsList Object containing initial params for each route.
*/
getStateForRouteNamesChange(
state: NavigationState,
options: {
routeNames: string[];
initialRouteName: string;
initialParamsList: ParamListBase;
}
): NavigationState;

/**
* Take the current state and action, and return a new state.
* If the action cannot be handled, return `null`.
Expand Down
21 changes: 21 additions & 0 deletions src/useNavigationBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type Options = {
children: React.ReactNode;
};

const isArrayEqual = (a: any[], b: any[]) =>
a.length === b.length && a.every((it, index) => it === b[index]);

export default function useNavigationBuilder(
router: Router<any>,
options: Options
Expand Down Expand Up @@ -78,6 +81,24 @@ export default function useNavigationBuilder(
partialState: currentState,
});

if (!isArrayEqual(state.routeNames, routeNames)) {
// When the list of route names change, the router should handle it to remove invalid routes
const nextState = router.getStateForRouteNamesChange(state, {
routeNames,
initialRouteName,
initialParamsList,
});

if (state !== nextState) {
// If the state needs to be updated, we'll schedule an update with React
// setState in render seems hacky, but that's how React docs implement getDerivedPropsFromState
// https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
setState(nextState);
}

state = nextState;
}

React.useEffect(() => {
return () => {
// We need to clean up state for this navigator on unmount
Expand Down

0 comments on commit b775dba

Please sign in to comment.