Skip to content
Permalink
Browse files

Clean up NavigationStateUtils

Summary:
== API Breaking Change ==

- Add unit tests to ensure that NavigationStateUtils does the right thing.
- Remove the logics that lets NavigationStateUtils accept empty value as input
  and return a new state.
- Remove the method `NavigationStateUtils.getParent`, `NavigationStateUtils.set`. These methods are rarely used and they can be replaced by other methods.

Reviewed By: ericvicenti

Differential Revision: D3374934

fbshipit-source-id: 0fdf538d014d7c5b4aa1f15a0ee8db9dc91e33cd
  • Loading branch information
Hedger Wang Facebook Github Bot 4
Hedger Wang authored and Facebook Github Bot 4 committed Jun 8, 2016
1 parent 30e9c40 commit 67002e8ae3c924bbba2e742dc1292b45f838a004
@@ -44,13 +44,26 @@ export type UIExplorerNavigationState = {

const defaultGetReducerForState = (initialState) => (state) => state || initialState;

function getNavigationState(state: any): ?NavigationState {
if (
(state instanceof Object) &&
(state.routes instanceof Array) &&
(state.routes[0] !== undefined) &&
(typeof state.index === 'number') &&
(state.routes[state.index] !== undefined)
) {
return state;
}
return null;
}

function StackReducer({initialState, getReducerForState, getPushedReducerForAction}: any): Function {
const getReducerForStateWithDefault = getReducerForState || defaultGetReducerForState;
return function (lastState: ?NavigationState, action: any): NavigationState {
if (!lastState) {
return initialState;
}
const lastParentState = NavigationStateUtils.getParent(lastState);
const lastParentState = getNavigationState(lastState);
if (!lastParentState) {
return lastState;
}
@@ -18,166 +18,180 @@ import type {
NavigationState,
} from 'NavigationTypeDefinition';

function getParent(state: NavigationState): ?NavigationState {
if (
(state instanceof Object) &&
(state.routes instanceof Array) &&
(state.routes[0] !== undefined) &&
(typeof state.index === 'number') &&
(state.routes[state.index] !== undefined)
) {
return state;
}
return null;
}
/**
* Utilities to perform atomic operation with navigate state and routes.
*/

/**
* Gets a route by key
*/
function get(state: NavigationState, key: string): ?NavigationRoute {
const parentState = getParent(state);
if (!parentState) {
return null;
}
const childState = parentState.routes.find(child => child.key === key);
return childState || null;
return state.routes.find(route => route.key === key) || null;
}

function indexOf(state: NavigationState, key: string): ?number {
const parentState = getParent(state);
if (!parentState) {
return null;
}
const index = parentState.routes.map(child => child.key).indexOf(key);
if (index === -1) {
return null;
}
return index;
/**
* Returns the first index at which a given route's key can be found in the
* routes of the navigation state, or -1 if it is not present.
*/
function indexOf(state: NavigationState, key: string): number {
return state.routes.map(route => route.key).indexOf(key);
}

/**
* Returns `true` at which a given route's key can be found in the
* routes of the navigation state.
*/
function has(state: NavigationState, key: string): boolean {
return !!state.routes.some(route => route.key === key);
}

function push(state: NavigationState, newChildState: NavigationRoute): NavigationState {
var lastChildren: Array<NavigationRoute> = state.routes;
/**
* Pushes a new route into the navigation state.
* Note that this moves the index to the positon to where the last route in the
* stack is at.
*/
function push(state: NavigationState, route: NavigationRoute): NavigationState {
invariant(
indexOf(state, route.key) === -1,
'should not push route with duplicated key %s',
route.key,
);

const routes = [
...state.routes,
route,
];

return {
...state,
routes: [
...lastChildren,
newChildState,
],
index: lastChildren.length,
index: routes.length - 1,
routes,
};
}

/**
* Pops out a route from the navigation state.
* Note that this moves the index to the positon to where the last route in the
* stack is at.
*/
function pop(state: NavigationState): NavigationState {
if (state.index <= 0) {
// [Note]: Over-popping does not throw error. Instead, it will be no-op.
return state;
}
const lastChildren = state.routes;
const routes = state.routes.slice(0, -1);
return {
...state,
routes: lastChildren.slice(0, lastChildren.length - 1),
index: lastChildren.length - 2,
index: routes.length - 1,
routes,
};
}

function reset(state: NavigationState, nextChildren: ?Array<NavigationRoute>, nextIndex: ?number): NavigationState {
const parentState = getParent(state);
if (!parentState) {
return state;
}
const routes = nextChildren || parentState.routes;
const index = nextIndex == null ? parentState.index : nextIndex;
if (routes === parentState.routes && index === parentState.index) {
/**
* Sets the focused route of the navigation state by index.
*/
function jumpToIndex(state: NavigationState, index: number): NavigationState {
if (index === state.index) {
return state;
}

invariant(!!state.routes[index], 'invalid index %s to jump to', index);

return {
...parentState,
routes,
...state,
index,
};
}

function set(state: ?NavigationState, key: string, nextChildren: Array<NavigationRoute>, nextIndex: number): NavigationState {
if (!state) {
return {
routes: nextChildren,
index: nextIndex,
};
}
const parentState = getParent(state);
if (!parentState) {
return {
routes: nextChildren,
index: nextIndex,
};
}
if (nextChildren === parentState.routes && nextIndex === parentState.index) {
return parentState;
}
return {
...parentState,
routes: nextChildren,
index: nextIndex,
};
/**
* Sets the focused route of the navigation state by key.
*/
function jumpTo(state: NavigationState, key: string): NavigationState {
const index = indexOf(state, key);
return jumpToIndex(state, index);
}

function jumpToIndex(state: NavigationState, index: number): NavigationState {
const parentState = getParent(state);
if (parentState && parentState.index === index) {
return parentState;
}
return {
...parentState,
index,
};
/**
* Replace a route by a key.
* Note that this moves the index to the positon to where the new route in the
* stack is at.
*/
function replaceAt(
state: NavigationState,
key: string,
route: NavigationRoute,
): NavigationState {
const index = indexOf(state, key);
return replaceAtIndex(state, index, route);
}

function jumpTo(state: NavigationState, key: string): NavigationState {
const parentState = getParent(state);
if (!parentState) {
return state;
}
const index = parentState.routes.indexOf(parentState.routes.find(child => child.key === key));
/**
* Replace a route by a index.
* Note that this moves the index to the positon to where the new route in the
* stack is at.
*/
function replaceAtIndex(
state: NavigationState,
index: number,
route: NavigationRoute,
): NavigationState {
invariant(
index !== -1,
'Cannot find child with matching key in this NavigationRoute'
);
return {
...parentState,
!!state.routes[index],
'invalid index %s for replacing route %s',
index,
};
}
route.key,
);

function replaceAt(state: NavigationState, key: string, newState: NavigationRoute): NavigationState {
const parentState = getParent(state);
if (!parentState) {
if (state.routes[index] === route) {
return state;
}
const routes = [...parentState.routes];
const index = parentState.routes.indexOf(parentState.routes.find(child => child.key === key));
invariant(
index !== -1,
'Cannot find child with matching key in this NavigationRoute'
);
routes[index] = newState;

const routes = state.routes.slice();
routes[index] = route;

return {
...parentState,
...state,
index,
routes,
};
}

function replaceAtIndex(state: NavigationState, index: number, newState: NavigationRoute): NavigationState {
const parentState = getParent(state);
if (!parentState) {
return state;
/**
* Resets all routes.
* Note that this moves the index to the positon to where the last route in the
* stack is at if the param `index` isn't provided.
*/
function reset(
state: NavigationState,
routes: Array<NavigationRoute>,
index?: number,
): NavigationState {
invariant(
routes.length && Array.isArray(routes),
'invalid routes to replace',
);

const nextIndex: number = index === undefined ? routes.length - 1 : index;

if (state.routes.length === routes.length && state.index === nextIndex) {
const compare = (route, ii) => routes[ii] === route;
if (state.routes.every(compare)) {
return state;
}
}
const routes = [...parentState.routes];
routes[index] = newState;

invariant(!!routes[nextIndex], 'invalid index %s to reset', nextIndex);

return {
...parentState,
...state,
index: nextIndex,
routes,
};
}

const NavigationStateUtils = {
get: get,
getParent,
has,
indexOf,
jumpTo,
jumpToIndex,
@@ -186,7 +200,6 @@ const NavigationStateUtils = {
replaceAt,
replaceAtIndex,
reset,
set: set,
};

module.exports = NavigationStateUtils;

1 comment on commit 67002e8

@steida

This comment has been minimized.

Copy link

steida commented on 67002e8 Jun 9, 2016

💯

Please sign in to comment.
You can’t perform that action at this time.