Actions creators overhaul (#3619)

This commit is contained in:
Eric Vicenti
2018-03-25 21:31:59 -04:00
committed by Brent Vatne
parent 99ac5b6c08
commit 670d48366b
21 changed files with 316 additions and 241 deletions

View File

@@ -5347,7 +5347,7 @@ react-native-tab-view@^0.0.74:
dependencies: dependencies:
prop-types "^15.6.0" prop-types "^15.6.0"
"react-native-tab-view@github:react-navigation/react-native-tab-view": react-native-tab-view@react-navigation/react-native-tab-view:
version "0.0.74" version "0.0.74"
resolved "https://codeload.github.com/react-navigation/react-native-tab-view/tar.gz/36ebd834d78b841fc19778c966465d02fd1213bb" resolved "https://codeload.github.com/react-navigation/react-native-tab-view/tar.gz/36ebd834d78b841fc19778c966465d02fd1213bb"
dependencies: dependencies:
@@ -5432,9 +5432,9 @@ react-navigation-header-buttons@^0.0.4:
dependencies: dependencies:
react-native-platform-touchable "^1.1.1" react-native-platform-touchable "^1.1.1"
react-navigation-tabs@^0.1.0-alpha.1: react-navigation-tabs@0.1.0-alpha.3:
version "0.1.0-alpha.1" version "0.1.0-alpha.3"
resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.1.0-alpha.1.tgz#23188655fe70376afd9de51992294d72f4fcbd20" resolved "https://registry.yarnpkg.com/react-navigation-tabs/-/react-navigation-tabs-0.1.0-alpha.3.tgz#a68ace954e84206b7c3e6f5793f234d3931d03dc"
dependencies: dependencies:
hoist-non-react-statics "^2.5.0" hoist-non-react-statics "^2.5.0"
prop-types "^15.6.0" prop-types "^15.6.0"

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { addNavigationHelpers, StackNavigator } from 'react-navigation'; import { StackNavigator } from 'react-navigation';
import LoginScreen from '../components/LoginScreen'; import LoginScreen from '../components/LoginScreen';
import MainScreen from '../components/MainScreen'; import MainScreen from '../components/MainScreen';
@@ -24,11 +24,11 @@ class AppWithNavigationState extends React.Component {
const { dispatch, nav } = this.props; const { dispatch, nav } = this.props;
return ( return (
<AppNavigator <AppNavigator
navigation={addNavigationHelpers({ navigation={{
dispatch, dispatch,
state: nav, state: nav,
addListener, addListener,
})} }}
/> />
); );
} }

View File

@@ -1,30 +1,15 @@
const BACK = 'Navigation/BACK'; const BACK = 'Navigation/BACK';
const INIT = 'Navigation/INIT'; const INIT = 'Navigation/INIT';
const NAVIGATE = 'Navigation/NAVIGATE'; const NAVIGATE = 'Navigation/NAVIGATE';
const POP = 'Navigation/POP';
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
const PUSH = 'Navigation/PUSH';
const RESET = 'Navigation/RESET';
const REPLACE = 'Navigation/REPLACE';
const SET_PARAMS = 'Navigation/SET_PARAMS'; const SET_PARAMS = 'Navigation/SET_PARAMS';
const URI = 'Navigation/URI';
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
const createAction = (type, fn) => { const back = (payload = {}) => ({
fn.toString = () => type;
return fn;
};
const back = createAction(BACK, (payload = {}) => ({
type: BACK, type: BACK,
key: payload.key, key: payload.key,
immediate: payload.immediate, immediate: payload.immediate,
})); });
const init = createAction(INIT, (payload = {}) => { const init = (payload = {}) => {
const action = { const action = {
type: INIT, type: INIT,
}; };
@@ -32,9 +17,9 @@ const init = createAction(INIT, (payload = {}) => {
action.params = payload.params; action.params = payload.params;
} }
return action; return action;
}); };
const navigate = createAction(NAVIGATE, payload => { const navigate = payload => {
const action = { const action = {
type: NAVIGATE, type: NAVIGATE,
routeName: payload.routeName, routeName: payload.routeName,
@@ -49,107 +34,24 @@ const navigate = createAction(NAVIGATE, payload => {
action.key = payload.key; action.key = payload.key;
} }
return action; return action;
}); };
const pop = createAction(POP, payload => ({ const setParams = payload => ({
type: POP,
n: payload && payload.n,
immediate: payload && payload.immediate,
}));
const popToTop = createAction(POP_TO_TOP, payload => ({
type: POP_TO_TOP,
immediate: payload && payload.immediate,
key: payload && payload.key,
}));
const push = createAction(PUSH, payload => {
const action = {
type: PUSH,
routeName: payload.routeName,
};
if (payload.params) {
action.params = payload.params;
}
if (payload.action) {
action.action = payload.action;
}
return action;
});
const reset = createAction(RESET, payload => ({
type: RESET,
index: payload.index,
key: payload.key,
actions: payload.actions,
}));
const replace = createAction(REPLACE, payload => ({
type: REPLACE,
key: payload.key,
newKey: payload.newKey,
params: payload.params,
action: payload.action,
routeName: payload.routeName,
immediate: payload.immediate,
}));
const setParams = createAction(SET_PARAMS, payload => ({
type: SET_PARAMS, type: SET_PARAMS,
key: payload.key, key: payload.key,
params: payload.params, params: payload.params,
})); });
const uri = createAction(URI, payload => ({
type: URI,
uri: payload.uri,
}));
const completeTransition = createAction(COMPLETE_TRANSITION, payload => ({
type: COMPLETE_TRANSITION,
key: payload && payload.key,
}));
const openDrawer = createAction(OPEN_DRAWER, payload => ({
type: OPEN_DRAWER,
}));
const closeDrawer = createAction(CLOSE_DRAWER, payload => ({
type: CLOSE_DRAWER,
}));
const toggleDrawer = createAction(TOGGLE_DRAWER, payload => ({
type: TOGGLE_DRAWER,
}));
export default { export default {
// Action constants // Action constants
BACK, BACK,
INIT, INIT,
NAVIGATE, NAVIGATE,
POP,
POP_TO_TOP,
PUSH,
RESET,
REPLACE,
SET_PARAMS, SET_PARAMS,
URI,
COMPLETE_TRANSITION,
OPEN_DRAWER,
CLOSE_DRAWER,
TOGGLE_DRAWER,
// Action creators // Action creators
back, back,
init, init,
navigate, navigate,
pop,
popToTop,
push,
reset,
replace,
setParams, setParams,
uri,
completeTransition,
openDrawer,
closeDrawer,
toggleDrawer,
}; };

View File

@@ -1,11 +1,10 @@
import NavigationActions from '../NavigationActions'; import NavigationActions from '../NavigationActions';
describe('actions', () => { describe('generic navigation actions', () => {
const params = { foo: 'bar' }; const params = { foo: 'bar' };
const navigateAction = NavigationActions.navigate({ routeName: 'another' }); const navigateAction = NavigationActions.navigate({ routeName: 'another' });
it('exports back action and type', () => { it('exports back action and type', () => {
expect(NavigationActions.back.toString()).toEqual(NavigationActions.BACK);
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK }); expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
expect(NavigationActions.back({ key: 'test' })).toEqual({ expect(NavigationActions.back({ key: 'test' })).toEqual({
type: NavigationActions.BACK, type: NavigationActions.BACK,
@@ -14,7 +13,6 @@ describe('actions', () => {
}); });
it('exports init action and type', () => { it('exports init action and type', () => {
expect(NavigationActions.init.toString()).toEqual(NavigationActions.INIT);
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT }); expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
expect(NavigationActions.init({ params })).toEqual({ expect(NavigationActions.init({ params })).toEqual({
type: NavigationActions.INIT, type: NavigationActions.INIT,
@@ -23,9 +21,6 @@ describe('actions', () => {
}); });
it('exports navigate action and type', () => { it('exports navigate action and type', () => {
expect(NavigationActions.navigate.toString()).toEqual(
NavigationActions.NAVIGATE
);
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({ expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
type: NavigationActions.NAVIGATE, type: NavigationActions.NAVIGATE,
routeName: 'test', routeName: 'test',
@@ -47,36 +42,7 @@ describe('actions', () => {
}); });
}); });
it('exports reset action and type', () => {
expect(NavigationActions.reset.toString()).toEqual(NavigationActions.RESET);
expect(NavigationActions.reset({ index: 0, actions: [] })).toEqual({
type: NavigationActions.RESET,
index: 0,
actions: [],
});
expect(
NavigationActions.reset({
index: 0,
key: 'test',
actions: [navigateAction],
})
).toEqual({
type: NavigationActions.RESET,
index: 0,
key: 'test',
actions: [
{
type: NavigationActions.NAVIGATE,
routeName: 'another',
},
],
});
});
it('exports setParams action and type', () => { it('exports setParams action and type', () => {
expect(NavigationActions.setParams.toString()).toEqual(
NavigationActions.SET_PARAMS
);
expect( expect(
NavigationActions.setParams({ NavigationActions.setParams({
key: 'test', key: 'test',
@@ -88,12 +54,4 @@ describe('actions', () => {
params, params,
}); });
}); });
it('exports uri action and type', () => {
expect(NavigationActions.uri.toString()).toEqual(NavigationActions.URI);
expect(NavigationActions.uri({ uri: 'http://google.com' })).toEqual({
type: NavigationActions.URI,
uri: 'http://google.com',
});
});
}); });

View File

@@ -4,7 +4,6 @@ import withLifecyclePolyfill from 'react-lifecycles-compat';
import { BackHandler } from './PlatformHelpers'; import { BackHandler } from './PlatformHelpers';
import NavigationActions from './NavigationActions'; import NavigationActions from './NavigationActions';
import addNavigationHelpers from './addNavigationHelpers';
import invariant from './utils/invariant'; import invariant from './utils/invariant';
import docsUrl from './utils/docsUrl'; import docsUrl from './utils/docsUrl';
@@ -327,7 +326,7 @@ export default function createNavigationContainer(Component) {
return this._renderLoading(); return this._renderLoading();
} }
if (!this._navigation || this._navigation.state !== nav) { if (!this._navigation || this._navigation.state !== nav) {
this._navigation = addNavigationHelpers({ this._navigation = {
dispatch: this.dispatch, dispatch: this.dispatch,
state: nav, state: nav,
addListener: (eventName, handler) => { addListener: (eventName, handler) => {
@@ -341,7 +340,7 @@ export default function createNavigationContainer(Component) {
}, },
}; };
}, },
}); };
} }
navigation = this._navigation; navigation = this._navigation;
} }

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import getChildEventSubscriber from '../getChildEventSubscriber'; import getChildEventSubscriber from '../getChildEventSubscriber';
import addNavigationHelpers from '../addNavigationHelpers';
function createNavigator(NavigatorView, router, navigationConfig) { function createNavigator(NavigatorView, router, navigationConfig) {
class Navigator extends React.Component { class Navigator extends React.Component {
@@ -53,13 +52,35 @@ function createNavigator(NavigatorView, router, navigationConfig) {
); );
} }
const childNavigation = addNavigationHelpers({ const actionCreators = {
...navigation.actions,
...router.getActionCreators(route, state.key),
};
const actionHelpers = {};
Object.keys(actionCreators).forEach(actionName => {
actionHelpers[actionName] = (...args) => {
const actionCreator = actionCreators[actionName];
const action = actionCreator(...args);
dispatch(action);
};
});
const childNavigation = {
...actionHelpers,
actions: actionCreators,
dispatch, dispatch,
state: route, state: route,
dangerouslyGetParent: this._dangerouslyGetParent, dangerouslyGetParent: this._dangerouslyGetParent,
addListener: this.childEventSubscribers[route.key].addListener, addListener: this.childEventSubscribers[route.key].addListener,
isFocused: () => this._isRouteFocused(route), getParam: (paramName, defaultValue) => {
}); const params = route.params;
if (params && paramName in params) {
return params[paramName];
}
return defaultValue;
},
};
const options = router.getScreenOptions(childNavigation, screenProps); const options = router.getScreenOptions(childNavigation, screenProps);
descriptors[route.key] = { descriptors[route.key] = {

View File

@@ -8,12 +8,6 @@ module.exports = {
get StateUtils() { get StateUtils() {
return require('./StateUtils').default; return require('./StateUtils').default;
}, },
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators // Navigators
get createNavigator() { get createNavigator() {
@@ -69,6 +63,17 @@ module.exports = {
return require('react-navigation-tabs').createMaterialTopTabNavigator; return require('react-navigation-tabs').createMaterialTopTabNavigator;
}, },
// Actions
get NavigationActions() {
return require('./NavigationActions').default;
},
get StackActions() {
return require('./routers/StackActions').default;
},
get DrawerActions() {
return require('./routers/DrawerActions').default;
},
// Routers // Routers
get StackRouter() { get StackRouter() {
return require('./routers/StackRouter').default; return require('./routers/StackRouter').default;

View File

@@ -8,18 +8,23 @@ module.exports = {
get StateUtils() { get StateUtils() {
return require('./StateUtils').default; return require('./StateUtils').default;
}, },
get addNavigationHelpers() {
return require('./addNavigationHelpers').default;
},
get NavigationActions() {
return require('./NavigationActions').default;
},
// Navigators // Navigators
get createNavigator() { get createNavigator() {
return require('./navigators/createNavigator').default; return require('./navigators/createNavigator').default;
}, },
// Actions
get NavigationActions() {
return require('./NavigationActions').default;
},
get StackActions() {
return require('./routers/StackActions').default;
},
get DrawerActions() {
return require('./routers/DrawerActions').default;
},
// Routers // Routers
get StackRouter() { get StackRouter() {
return require('./routers/StackRouter').default; return require('./routers/StackRouter').default;
@@ -27,6 +32,9 @@ module.exports = {
get TabRouter() { get TabRouter() {
return require('./routers/TabRouter').default; return require('./routers/TabRouter').default;
}, },
get SwitchRouter() {
return require('./routers/SwitchRouter').default;
},
// HOCs // HOCs
get withNavigation() { get withNavigation() {

View File

@@ -0,0 +1,28 @@
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
const openDrawer = payload => ({
type: OPEN_DRAWER,
...payload,
});
const closeDrawer = payload => ({
type: CLOSE_DRAWER,
...payload,
});
const toggleDrawer = payload => ({
type: TOGGLE_DRAWER,
...payload,
});
export default {
OPEN_DRAWER,
CLOSE_DRAWER,
TOGGLE_DRAWER,
openDrawer,
closeDrawer,
toggleDrawer,
};

View File

@@ -4,6 +4,8 @@ import NavigationActions from '../NavigationActions';
import invariant from '../utils/invariant'; import invariant from '../utils/invariant';
import withDefaultValue from '../utils/withDefaultValue'; import withDefaultValue from '../utils/withDefaultValue';
import DrawerActions from './DrawerActions';
export default (routeConfigs, config = {}) => { export default (routeConfigs, config = {}) => {
config = { ...config }; config = { ...config };
config = withDefaultValue(config, 'resetOnBlur', false); config = withDefaultValue(config, 'resetOnBlur', false);
@@ -14,6 +16,15 @@ export default (routeConfigs, config = {}) => {
return { return {
...switchRouter, ...switchRouter,
getActionCreators(route, navStateKey) {
return {
openDrawer: () => ({ type: DrawerActions.OPEN_DRAWER }),
closeDrawer: () => ({ type: DrawerActions.CLOSE_DRAWER }),
toggleDrawer: () => ({ type: DrawerActions.TOGGLE_DRAWER }),
...switchRouter.getActionCreators(route, navStateKey),
};
},
getStateForAction(action, lastState) { getStateForAction(action, lastState) {
const state = lastState || { const state = lastState || {
...switchRouter.getStateForAction(action, undefined), ...switchRouter.getStateForAction(action, undefined),
@@ -21,25 +32,19 @@ export default (routeConfigs, config = {}) => {
}; };
// Handle explicit drawer actions // Handle explicit drawer actions
if ( if (state.isDrawerOpen && action.type === DrawerActions.CLOSE_DRAWER) {
state.isDrawerOpen &&
action.type === NavigationActions.CLOSE_DRAWER
) {
return { return {
...state, ...state,
isDrawerOpen: false, isDrawerOpen: false,
}; };
} }
if ( if (!state.isDrawerOpen && action.type === DrawerActions.OPEN_DRAWER) {
!state.isDrawerOpen &&
action.type === NavigationActions.OPEN_DRAWER
) {
return { return {
...state, ...state,
isDrawerOpen: true, isDrawerOpen: true,
}; };
} }
if (action.type === NavigationActions.TOGGLE_DRAWER) { if (action.type === DrawerActions.TOGGLE_DRAWER) {
return { return {
...state, ...state,
isDrawerOpen: !state.isDrawerOpen, isDrawerOpen: !state.isDrawerOpen,

View File

@@ -0,0 +1,52 @@
const POP = 'Navigation/POP';
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
const PUSH = 'Navigation/PUSH';
const RESET = 'Navigation/RESET';
const REPLACE = 'Navigation/REPLACE';
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
const pop = payload => ({
type: POP,
...payload,
});
const popToTop = payload => ({
type: POP_TO_TOP,
...payload,
});
const push = payload => ({
type: PUSH,
...payload,
});
const reset = payload => ({
type: RESET,
...payload,
});
const replace = payload => ({
type: REPLACE,
...payload,
});
const completeTransition = payload => ({
type: COMPLETE_TRANSITION,
...payload,
});
export default {
POP,
POP_TO_TOP,
PUSH,
RESET,
REPLACE,
COMPLETE_TRANSITION,
pop,
popToTop,
push,
reset,
replace,
completeTransition,
};

View File

@@ -1,12 +1,14 @@
import pathToRegexp from 'path-to-regexp'; import pathToRegexp from 'path-to-regexp';
import NavigationActions from '../NavigationActions'; import NavigationActions from '../NavigationActions';
import StackActions from './StackActions';
import createConfigGetter from './createConfigGetter'; import createConfigGetter from './createConfigGetter';
import getScreenForRouteName from './getScreenForRouteName'; import getScreenForRouteName from './getScreenForRouteName';
import StateUtils from '../StateUtils'; import StateUtils from '../StateUtils';
import validateRouteConfigMap from './validateRouteConfigMap'; import validateRouteConfigMap from './validateRouteConfigMap';
import invariant from '../utils/invariant'; import invariant from '../utils/invariant';
import { generateKey } from './KeyGenerator'; import { generateKey } from './KeyGenerator';
import getNavigationActionCreators from './getNavigationActionCreators';
function isEmpty(obj) { function isEmpty(obj) {
if (!obj) return true; if (!obj) return true;
@@ -19,7 +21,7 @@ function isEmpty(obj) {
function behavesLikePushAction(action) { function behavesLikePushAction(action) {
return ( return (
action.type === NavigationActions.NAVIGATE || action.type === NavigationActions.NAVIGATE ||
action.type === NavigationActions.PUSH action.type === StackActions.PUSH
); );
} }
@@ -42,7 +44,7 @@ export default (routeConfigs, stackConfig = {}) => {
} }
}); });
const { initialRouteParams } = stackConfig; const { initialRouteParams, getActionCreators } = stackConfig;
const initialRouteName = stackConfig.initialRouteName || routeNames[0]; const initialRouteName = stackConfig.initialRouteName || routeNames[0];
@@ -136,7 +138,7 @@ export default (routeConfigs, stackConfig = {}) => {
}); });
paths = Object.entries(pathsByRouteNames); paths = Object.entries(pathsByRouteNames);
paths.sort((a: [string, *], b: [string, *]) => b[1].priority - a[1].priority); paths.sort((a, b) => b[1].priority - a[1].priority);
return { return {
getComponentForState(state) { getComponentForState(state) {
@@ -152,6 +154,45 @@ export default (routeConfigs, stackConfig = {}) => {
return getScreenForRouteName(routeConfigs, routeName); return getScreenForRouteName(routeConfigs, routeName);
}, },
getActionCreators(route, navStateKey) {
return {
...getNavigationActionCreators(route, navStateKey),
...(getActionCreators ? getActionCreators(route, navStateKey) : {}),
pop: (n, params) => ({
type: StackActions.POP,
n,
...params,
}),
popToTop: params => ({
type: StackActions.POP_TO_TOP,
...params,
}),
push: (routeName, params, action) => ({
type: StackActions.PUSH,
routeName,
params,
action,
}),
replace: (routeName, params, action) => ({
type: StackActions.REPLACE,
routeName,
params,
action,
key: route.key,
}),
reset: (actions, index) => ({
type: StackActions.RESET,
actions,
index: index == null ? actions.length - 1 : index,
key: navStateKey,
}),
dismiss: () => ({
type: NavigationActions.BACK,
key: navStateKey,
}),
};
},
getStateForAction(action, state) { getStateForAction(action, state) {
// Set up the initial state if needed // Set up the initial state if needed
if (!state) { if (!state) {
@@ -160,7 +201,7 @@ export default (routeConfigs, stackConfig = {}) => {
// Check if the focused child scene wants to handle the action, as long as // Check if the focused child scene wants to handle the action, as long as
// it is not a reset to the root stack // it is not a reset to the root stack
if (action.type !== NavigationActions.RESET || action.key !== null) { if (action.type !== StackActions.RESET || action.key !== null) {
const keyIndex = action.key const keyIndex = action.key
? StateUtils.indexOf(state, action.key) ? StateUtils.indexOf(state, action.key)
: -1; : -1;
@@ -192,7 +233,7 @@ export default (routeConfigs, stackConfig = {}) => {
let route; let route;
invariant( invariant(
action.type !== NavigationActions.PUSH || action.key == null, action.type !== StackActions.PUSH || action.key == null,
'StackRouter does not support key on the push action' 'StackRouter does not support key on the push action'
); );
@@ -206,7 +247,7 @@ export default (routeConfigs, stackConfig = {}) => {
} }
}); });
if (action.type !== NavigationActions.PUSH && lastRouteIndex !== -1) { if (action.type !== StackActions.PUSH && lastRouteIndex !== -1) {
// If index is unchanged and params are not being set, leave state identity intact // If index is unchanged and params are not being set, leave state identity intact
if (state.index === lastRouteIndex && !action.params) { if (state.index === lastRouteIndex && !action.params) {
return null; return null;
@@ -260,7 +301,7 @@ export default (routeConfigs, stackConfig = {}) => {
isTransitioning: action.immediate !== true, isTransitioning: action.immediate !== true,
}; };
} else if ( } else if (
action.type === NavigationActions.PUSH && action.type === StackActions.PUSH &&
childRouters[action.routeName] === undefined childRouters[action.routeName] === undefined
) { ) {
// Return the state identity to bubble the action up // Return the state identity to bubble the action up
@@ -304,7 +345,7 @@ export default (routeConfigs, stackConfig = {}) => {
} }
// Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first. // Handle pop-to-top behavior. Make sure this happens after children have had a chance to handle the action, so that the inner stack pops to top first.
if (action.type === NavigationActions.POP_TO_TOP) { if (action.type === StackActions.POP_TO_TOP) {
// Refuse to handle pop to top if a key is given that doesn't correspond // Refuse to handle pop to top if a key is given that doesn't correspond
// to this router // to this router
if (action.key && state.key !== action.key) { if (action.key && state.key !== action.key) {
@@ -325,7 +366,7 @@ export default (routeConfigs, stackConfig = {}) => {
} }
// Handle replace action // Handle replace action
if (action.type === NavigationActions.REPLACE) { if (action.type === StackActions.REPLACE) {
const routeIndex = state.routes.findIndex(r => r.key === action.key); const routeIndex = state.routes.findIndex(r => r.key === action.key);
// Only replace if the key matches one of our routes // Only replace if the key matches one of our routes
if (routeIndex !== -1) { if (routeIndex !== -1) {
@@ -351,7 +392,7 @@ export default (routeConfigs, stackConfig = {}) => {
// Update transitioning state // Update transitioning state
if ( if (
action.type === NavigationActions.COMPLETE_TRANSITION && action.type === StackActions.COMPLETE_TRANSITION &&
(action.key == null || action.key === state.key) && (action.key == null || action.key === state.key) &&
state.isTransitioning state.isTransitioning
) { ) {
@@ -381,7 +422,7 @@ export default (routeConfigs, stackConfig = {}) => {
} }
} }
if (action.type === NavigationActions.RESET) { if (action.type === StackActions.RESET) {
// Only handle reset actions that are unspecified or match this state key // Only handle reset actions that are unspecified or match this state key
if (action.key != null && action.key != state.key) { if (action.key != null && action.key != state.key) {
// Deliberately use != instead of !== so we can match null with // Deliberately use != instead of !== so we can match null with
@@ -418,11 +459,11 @@ export default (routeConfigs, stackConfig = {}) => {
if ( if (
action.type === NavigationActions.BACK || action.type === NavigationActions.BACK ||
action.type === NavigationActions.POP action.type === StackActions.POP
) { ) {
const { key, n, immediate } = action; const { key, n, immediate } = action;
let backRouteIndex = state.index; let backRouteIndex = state.index;
if (action.type === NavigationActions.POP && n != null) { if (action.type === StackActions.POP && n != null) {
// determine the index to go back *from*. In this case, n=1 means to go // determine the index to go back *from*. In this case, n=1 means to go
// back from state.index, as if it were a normal "BACK" action // back from state.index, as if it were a normal "BACK" action
backRouteIndex = Math.max(1, state.index - n + 1); backRouteIndex = Math.max(1, state.index - n + 1);

View File

@@ -3,12 +3,15 @@ import getScreenForRouteName from './getScreenForRouteName';
import createConfigGetter from './createConfigGetter'; import createConfigGetter from './createConfigGetter';
import NavigationActions from '../NavigationActions'; import NavigationActions from '../NavigationActions';
import StackActions from './StackActions';
import validateRouteConfigMap from './validateRouteConfigMap'; import validateRouteConfigMap from './validateRouteConfigMap';
import getNavigationActionCreators from './getNavigationActionCreators';
function childrenUpdateWithoutSwitchingIndex(actionType) { function childrenUpdateWithoutSwitchingIndex(actionType) {
return [ return [
NavigationActions.SET_PARAMS, NavigationActions.SET_PARAMS,
NavigationActions.COMPLETE_TRANSITION, // Todo: make SwitchRouter not depend on StackActions..
StackActions.COMPLETE_TRANSITION,
].includes(actionType); ].includes(actionType);
} }
@@ -98,6 +101,10 @@ export default (routeConfigs, config = {}) => {
return nextState; return nextState;
}, },
getActionCreators(route, stateKey) {
return getNavigationActionCreators(route, stateKey);
},
getStateForAction(action, inputState) { getStateForAction(action, inputState) {
let prevState = inputState ? { ...inputState } : inputState; let prevState = inputState ? { ...inputState } : inputState;
let state = inputState || this.getInitialState(); let state = inputState || this.getInitialState();

View File

@@ -4,6 +4,7 @@ import React from 'react';
import DrawerRouter from '../DrawerRouter'; import DrawerRouter from '../DrawerRouter';
import NavigationActions from '../../NavigationActions'; import NavigationActions from '../../NavigationActions';
import DrawerActions from '../../routers/DrawerActions';
const INIT_ACTION = { type: NavigationActions.INIT }; const INIT_ACTION = { type: NavigationActions.INIT };
@@ -54,17 +55,17 @@ describe('DrawerRouter', () => {
const state = router.getStateForAction(INIT_ACTION); const state = router.getStateForAction(INIT_ACTION);
expect(state.isDrawerOpen).toEqual(false); expect(state.isDrawerOpen).toEqual(false);
const state2 = router.getStateForAction( const state2 = router.getStateForAction(
{ type: NavigationActions.OPEN_DRAWER }, { type: DrawerActions.OPEN_DRAWER },
state state
); );
expect(state2.isDrawerOpen).toEqual(true); expect(state2.isDrawerOpen).toEqual(true);
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ type: NavigationActions.CLOSE_DRAWER }, { type: DrawerActions.CLOSE_DRAWER },
state2 state2
); );
expect(state3.isDrawerOpen).toEqual(false); expect(state3.isDrawerOpen).toEqual(false);
const state4 = router.getStateForAction( const state4 = router.getStateForAction(
{ type: NavigationActions.TOGGLE_DRAWER }, { type: DrawerActions.TOGGLE_DRAWER },
state3 state3
); );
expect(state4.isDrawerOpen).toEqual(true); expect(state4.isDrawerOpen).toEqual(true);

View File

@@ -6,7 +6,6 @@ import StackRouter from '../StackRouter';
import TabRouter from '../TabRouter'; import TabRouter from '../TabRouter';
import NavigationActions from '../../NavigationActions'; import NavigationActions from '../../NavigationActions';
import addNavigationHelpers from '../../addNavigationHelpers';
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator'; import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
beforeEach(() => { beforeEach(() => {
@@ -58,31 +57,31 @@ Object.keys(ROUTERS).forEach(routerName => {
]; ];
expect( expect(
router.getScreenOptions( router.getScreenOptions(
addNavigationHelpers({ {
state: routes[0], state: routes[0],
dispatch: () => false, dispatch: () => false,
addListener: dummyEventSubscriber, addListener: dummyEventSubscriber,
}), },
{} {}
).title ).title
).toEqual(undefined); ).toEqual(undefined);
expect( expect(
router.getScreenOptions( router.getScreenOptions(
addNavigationHelpers({ {
state: routes[1], state: routes[1],
dispatch: () => false, dispatch: () => false,
addListener: dummyEventSubscriber, addListener: dummyEventSubscriber,
}), },
{} {}
).title ).title
).toEqual('BarTitle'); ).toEqual('BarTitle');
expect( expect(
router.getScreenOptions( router.getScreenOptions(
addNavigationHelpers({ {
state: routes[2], state: routes[2],
dispatch: () => false, dispatch: () => false,
addListener: dummyEventSubscriber, addListener: dummyEventSubscriber,
}), },
{} {}
).title ).title
).toEqual('Baz-123'); ).toEqual('Baz-123');

View File

@@ -3,11 +3,11 @@
import React from 'react'; import React from 'react';
import StackRouter from '../StackRouter'; import StackRouter from '../StackRouter';
import StackActions from '../StackActions';
import NavigationActions from '../../NavigationActions';
import TabRouter from '../TabRouter'; import TabRouter from '../TabRouter';
import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator'; import { _TESTING_ONLY_normalize_keys } from '../KeyGenerator';
import NavigationActions from '../../NavigationActions';
beforeEach(() => { beforeEach(() => {
_TESTING_ONLY_normalize_keys(); _TESTING_ONLY_normalize_keys();
}); });
@@ -388,7 +388,7 @@ describe('StackRouter', () => {
const barKey = state2.routes[1].routes[0].key; const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ {
type: NavigationActions.PUSH, type: StackActions.PUSH,
routeName: 'Bad', routeName: 'Bad',
}, },
state2 state2
@@ -420,7 +420,7 @@ describe('StackRouter', () => {
const barKey = state2.routes[1].routes[0].key; const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ {
type: NavigationActions.POP, type: StackActions.POP,
}, },
state2 state2
); );
@@ -448,7 +448,7 @@ describe('StackRouter', () => {
const barKey = state2.routes[1].routes[0].key; const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ {
type: NavigationActions.POP_TO_TOP, type: StackActions.POP_TO_TOP,
}, },
state2 state2
); );
@@ -476,7 +476,7 @@ describe('StackRouter', () => {
const barKey = state2.routes[1].routes[0].key; const barKey = state2.routes[1].routes[0].key;
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ {
type: NavigationActions.POP_TO_TOP, type: StackActions.POP_TO_TOP,
key: state2.key, key: state2.key,
}, },
state2 state2
@@ -500,19 +500,19 @@ describe('StackRouter', () => {
], ],
}; };
const poppedState = TestRouter.getStateForAction( const poppedState = TestRouter.getStateForAction(
NavigationActions.popToTop(), StackActions.popToTop(),
state state
); );
expect(poppedState.routes.length).toBe(1); expect(poppedState.routes.length).toBe(1);
expect(poppedState.index).toBe(0); expect(poppedState.index).toBe(0);
expect(poppedState.isTransitioning).toBe(true); expect(poppedState.isTransitioning).toBe(true);
const poppedState2 = TestRouter.getStateForAction( const poppedState2 = TestRouter.getStateForAction(
NavigationActions.popToTop(), StackActions.popToTop(),
poppedState poppedState
); );
expect(poppedState).toEqual(poppedState2); expect(poppedState).toEqual(poppedState2);
const poppedImmediatelyState = TestRouter.getStateForAction( const poppedImmediatelyState = TestRouter.getStateForAction(
NavigationActions.popToTop({ immediate: true }), StackActions.popToTop({ immediate: true }),
state state
); );
expect(poppedImmediatelyState.routes.length).toBe(1); expect(poppedImmediatelyState.routes.length).toBe(1);
@@ -681,14 +681,14 @@ describe('StackRouter', () => {
}); });
const initState = TestRouter.getStateForAction(NavigationActions.init()); const initState = TestRouter.getStateForAction(NavigationActions.init());
const pushedState = TestRouter.getStateForAction( const pushedState = TestRouter.getStateForAction(
NavigationActions.push({ routeName: 'bar' }), StackActions.push({ routeName: 'bar' }),
initState initState
); );
expect(pushedState.index).toEqual(1); expect(pushedState.index).toEqual(1);
expect(pushedState.routes[1].routeName).toEqual('bar'); expect(pushedState.routes[1].routeName).toEqual('bar');
expect(() => { expect(() => {
TestRouter.getStateForAction( TestRouter.getStateForAction(
{ type: NavigationActions.PUSH, routeName: 'bar', key: 'a' }, { type: StackActions.PUSH, routeName: 'bar', key: 'a' },
pushedState pushedState
); );
}).toThrow(); }).toThrow();
@@ -701,13 +701,13 @@ describe('StackRouter', () => {
}); });
const initState = TestRouter.getStateForAction(NavigationActions.init()); const initState = TestRouter.getStateForAction(NavigationActions.init());
const pushedState = TestRouter.getStateForAction( const pushedState = TestRouter.getStateForAction(
NavigationActions.push({ routeName: 'bar' }), StackActions.push({ routeName: 'bar' }),
initState initState
); );
expect(pushedState.index).toEqual(1); expect(pushedState.index).toEqual(1);
expect(pushedState.routes[1].routeName).toEqual('bar'); expect(pushedState.routes[1].routeName).toEqual('bar');
const secondPushedState = TestRouter.getStateForAction( const secondPushedState = TestRouter.getStateForAction(
NavigationActions.push({ routeName: 'bar' }), StackActions.push({ routeName: 'bar' }),
pushedState pushedState
); );
expect(secondPushedState.index).toEqual(2); expect(secondPushedState.index).toEqual(2);
@@ -807,7 +807,7 @@ describe('StackRouter', () => {
NavigationActions.navigate({ routeName: 'foo' }) NavigationActions.navigate({ routeName: 'foo' })
); );
const replacedState = TestRouter.getStateForAction( const replacedState = TestRouter.getStateForAction(
NavigationActions.replace({ StackActions.replace({
routeName: 'bar', routeName: 'bar',
params: { meaning: 42 }, params: { meaning: 42 },
key: initState.routes[0].key, key: initState.routes[0].key,
@@ -820,7 +820,7 @@ describe('StackRouter', () => {
expect(replacedState.routes[0].routeName).toEqual('bar'); expect(replacedState.routes[0].routeName).toEqual('bar');
expect(replacedState.routes[0].params.meaning).toEqual(42); expect(replacedState.routes[0].params.meaning).toEqual(42);
const replacedState2 = TestRouter.getStateForAction( const replacedState2 = TestRouter.getStateForAction(
NavigationActions.replace({ StackActions.replace({
routeName: 'bar', routeName: 'bar',
key: initState.routes[0].key, key: initState.routes[0].key,
newKey: 'wow', newKey: 'wow',
@@ -857,7 +857,7 @@ describe('StackRouter', () => {
expect(state2 && state2.isTransitioning).toEqual(true); expect(state2 && state2.isTransitioning).toEqual(true);
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ {
type: NavigationActions.COMPLETE_TRANSITION, type: StackActions.COMPLETE_TRANSITION,
}, },
state2 state2
); );
@@ -1125,7 +1125,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT }); const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction( const state2 = router.getStateForAction(
{ {
type: NavigationActions.RESET, type: StackActions.RESET,
actions: [ actions: [
{ {
type: NavigationActions.NAVIGATE, type: NavigationActions.NAVIGATE,
@@ -1160,7 +1160,7 @@ describe('StackRouter', () => {
}); });
const state1 = router.getStateForAction({ type: NavigationActions.INIT }); const state1 = router.getStateForAction({ type: NavigationActions.INIT });
const resetAction = { const resetAction = {
type: NavigationActions.RESET, type: StackActions.RESET,
key: 'Bad Key', key: 'Bad Key',
actions: [ actions: [
{ {
@@ -1213,7 +1213,7 @@ describe('StackRouter', () => {
const state = router.getStateForAction({ type: NavigationActions.INIT }); const state = router.getStateForAction({ type: NavigationActions.INIT });
const state2 = router.getStateForAction( const state2 = router.getStateForAction(
{ {
type: NavigationActions.RESET, type: StackActions.RESET,
actions: [ actions: [
{ {
type: NavigationActions.NAVIGATE, type: NavigationActions.NAVIGATE,
@@ -1265,7 +1265,7 @@ describe('StackRouter', () => {
); );
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ {
type: NavigationActions.RESET, type: StackActions.RESET,
key: 'Init', key: 'Init',
actions: [ actions: [
{ {
@@ -1280,7 +1280,7 @@ describe('StackRouter', () => {
); );
const state4 = router.getStateForAction( const state4 = router.getStateForAction(
{ {
type: NavigationActions.RESET, type: StackActions.RESET,
key: null, key: null,
actions: [ actions: [
{ {
@@ -1752,7 +1752,7 @@ test('Handles deep navigate completion action', () => {
expect(!!key).toEqual(true); expect(!!key).toEqual(true);
const state3 = router.getStateForAction( const state3 = router.getStateForAction(
{ {
type: NavigationActions.COMPLETE_TRANSITION, type: StackActions.COMPLETE_TRANSITION,
}, },
state2 state2
); );

View File

@@ -4,6 +4,7 @@ import React from 'react';
import TabRouter from '../TabRouter'; import TabRouter from '../TabRouter';
import StackRouter from '../StackRouter'; import StackRouter from '../StackRouter';
import StackActions from '../../routers/StackActions';
import NavigationActions from '../../NavigationActions'; import NavigationActions from '../../NavigationActions';
const INIT_ACTION = { type: NavigationActions.INIT }; const INIT_ACTION = { type: NavigationActions.INIT };
@@ -710,16 +711,13 @@ describe('TabRouter', () => {
{ key: 'D', routeName: 'bar' }, { key: 'D', routeName: 'bar' },
], ],
}; };
const poppedState = TestRouter.getStateForAction( const poppedState = TestRouter.getStateForAction(StackActions.pop(), state);
NavigationActions.pop(),
state
);
expect(poppedState.routes.length).toBe(3); expect(poppedState.routes.length).toBe(3);
expect(poppedState.index).toBe(2); expect(poppedState.index).toBe(2);
expect(poppedState.isTransitioning).toBe(true); expect(poppedState.isTransitioning).toBe(true);
const poppedState2 = TestRouter.getStateForAction( const poppedState2 = TestRouter.getStateForAction(
NavigationActions.pop({ n: 2, immediate: true }), StackActions.pop({ n: 2, immediate: true }),
state state
); );
expect(poppedState2.routes.length).toBe(2); expect(poppedState2.routes.length).toBe(2);
@@ -727,7 +725,7 @@ describe('TabRouter', () => {
expect(poppedState2.isTransitioning).toBe(false); expect(poppedState2.isTransitioning).toBe(false);
const poppedState3 = TestRouter.getStateForAction( const poppedState3 = TestRouter.getStateForAction(
NavigationActions.pop({ n: 5 }), StackActions.pop({ n: 5 }),
state state
); );
expect(poppedState3.routes.length).toBe(1); expect(poppedState3.routes.length).toBe(1);

View File

@@ -0,0 +1,49 @@
import NavigationActions from '../NavigationActions';
const getNavigationActionCreators = route => {
return {
goBack: key => {
let actualizedKey = key;
if (key === undefined && navigation.state.key) {
invariant(
typeof navigation.state.key === 'string',
'key should be a string'
);
actualizedKey = navigation.state.key;
}
return NavigationActions.back({ key: actualizedKey });
},
navigate: (navigateTo, params, action) => {
if (typeof navigateTo === 'string') {
return NavigationActions.navigate({
routeName: navigateTo,
params,
action,
});
}
invariant(
typeof navigateTo === 'object',
'Must navigateTo an object or a string'
);
invariant(
params == null,
'Params must not be provided to .navigate() when specifying an object'
);
invariant(
action == null,
'Child action must not be provided to .navigate() when specifying an object'
);
return NavigationActions.navigate(navigateTo);
},
setParams: params => {
invariant(
navigation.state.key && typeof navigation.state.key === 'string',
'setParams cannot be called by root navigator'
);
const key = navigation.state.key;
return NavigationActions.setParams({ params, key });
},
};
};
export default getNavigationActionCreators;

View File

@@ -2,9 +2,9 @@ import React from 'react';
import { Dimensions } from 'react-native'; import { Dimensions } from 'react-native';
import DrawerLayout from 'react-native-drawer-layout-polyfill'; import DrawerLayout from 'react-native-drawer-layout-polyfill';
import addNavigationHelpers from '../../addNavigationHelpers';
import DrawerSidebar from './DrawerSidebar'; import DrawerSidebar from './DrawerSidebar';
import NavigationActions from '../../NavigationActions'; import NavigationActions from '../../NavigationActions';
import DrawerActions from '../../routers/DrawerActions';
/** /**
* Component that renders the drawer. * Component that renders the drawer.
@@ -40,7 +40,7 @@ export default class DrawerView extends React.PureComponent {
const { navigation } = this.props; const { navigation } = this.props;
const { isDrawerOpen } = navigation.state; const { isDrawerOpen } = navigation.state;
if (!isDrawerOpen) { if (!isDrawerOpen) {
navigation.dispatch({ type: NavigationActions.OPEN_DRAWER }); navigation.dispatch({ type: DrawerActions.OPEN_DRAWER });
} }
}; };
@@ -48,7 +48,7 @@ export default class DrawerView extends React.PureComponent {
const { navigation } = this.props; const { navigation } = this.props;
const { isDrawerOpen } = navigation.state; const { isDrawerOpen } = navigation.state;
if (isDrawerOpen) { if (isDrawerOpen) {
navigation.dispatch({ type: NavigationActions.CLOSE_DRAWER }); navigation.dispatch({ type: DrawerActions.CLOSE_DRAWER });
} }
}; };

View File

@@ -4,6 +4,7 @@ import { NativeModules } from 'react-native';
import StackViewLayout from './StackViewLayout'; import StackViewLayout from './StackViewLayout';
import Transitioner from '../Transitioner'; import Transitioner from '../Transitioner';
import NavigationActions from '../../NavigationActions'; import NavigationActions from '../../NavigationActions';
import StackActions from '../../routers/StackActions';
import TransitionConfigs from './StackViewTransitionConfigs'; import TransitionConfigs from './StackViewTransitionConfigs';
const NativeAnimatedModule = const NativeAnimatedModule =
@@ -27,7 +28,7 @@ class StackView extends React.Component {
onTransitionEnd={(lastTransition, transition) => { onTransitionEnd={(lastTransition, transition) => {
const { onTransitionEnd, navigation } = this.props; const { onTransitionEnd, navigation } = this.props;
navigation.dispatch( navigation.dispatch(
NavigationActions.completeTransition({ StackActions.completeTransition({
key: navigation.state.key, key: navigation.state.key,
}) })
); );

View File

@@ -14,6 +14,7 @@ import {
import Card from './StackViewCard'; import Card from './StackViewCard';
import Header from '../Header/Header'; import Header from '../Header/Header';
import NavigationActions from '../../NavigationActions'; import NavigationActions from '../../NavigationActions';
import StackActions from '../../routers/StackActions';
import SceneView from '../SceneView'; import SceneView from '../SceneView';
import { NavigationProvider } from '../NavigationContext'; import { NavigationProvider } from '../NavigationContext';
@@ -178,7 +179,7 @@ class StackViewLayout extends React.Component {
immediate: true, immediate: true,
}) })
); );
navigation.dispatch(NavigationActions.completeTransition()); navigation.dispatch(StackActions.completeTransition());
} }
}; };