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

@@ -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 withDefaultValue from '../utils/withDefaultValue';
import DrawerActions from './DrawerActions';
export default (routeConfigs, config = {}) => {
config = { ...config };
config = withDefaultValue(config, 'resetOnBlur', false);
@@ -14,6 +16,15 @@ export default (routeConfigs, config = {}) => {
return {
...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) {
const state = lastState || {
...switchRouter.getStateForAction(action, undefined),
@@ -21,25 +32,19 @@ export default (routeConfigs, config = {}) => {
};
// Handle explicit drawer actions
if (
state.isDrawerOpen &&
action.type === NavigationActions.CLOSE_DRAWER
) {
if (state.isDrawerOpen && action.type === DrawerActions.CLOSE_DRAWER) {
return {
...state,
isDrawerOpen: false,
};
}
if (
!state.isDrawerOpen &&
action.type === NavigationActions.OPEN_DRAWER
) {
if (!state.isDrawerOpen && action.type === DrawerActions.OPEN_DRAWER) {
return {
...state,
isDrawerOpen: true,
};
}
if (action.type === NavigationActions.TOGGLE_DRAWER) {
if (action.type === DrawerActions.TOGGLE_DRAWER) {
return {
...state,
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 NavigationActions from '../NavigationActions';
import StackActions from './StackActions';
import createConfigGetter from './createConfigGetter';
import getScreenForRouteName from './getScreenForRouteName';
import StateUtils from '../StateUtils';
import validateRouteConfigMap from './validateRouteConfigMap';
import invariant from '../utils/invariant';
import { generateKey } from './KeyGenerator';
import getNavigationActionCreators from './getNavigationActionCreators';
function isEmpty(obj) {
if (!obj) return true;
@@ -19,7 +21,7 @@ function isEmpty(obj) {
function behavesLikePushAction(action) {
return (
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];
@@ -136,7 +138,7 @@ export default (routeConfigs, stackConfig = {}) => {
});
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 {
getComponentForState(state) {
@@ -152,6 +154,45 @@ export default (routeConfigs, stackConfig = {}) => {
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) {
// Set up the initial state if needed
if (!state) {
@@ -160,7 +201,7 @@ export default (routeConfigs, stackConfig = {}) => {
// Check if the focused child scene wants to handle the action, as long as
// 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
? StateUtils.indexOf(state, action.key)
: -1;
@@ -192,7 +233,7 @@ export default (routeConfigs, stackConfig = {}) => {
let route;
invariant(
action.type !== NavigationActions.PUSH || action.key == null,
action.type !== StackActions.PUSH || action.key == null,
'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 (state.index === lastRouteIndex && !action.params) {
return null;
@@ -260,7 +301,7 @@ export default (routeConfigs, stackConfig = {}) => {
isTransitioning: action.immediate !== true,
};
} else if (
action.type === NavigationActions.PUSH &&
action.type === StackActions.PUSH &&
childRouters[action.routeName] === undefined
) {
// 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.
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
// to this router
if (action.key && state.key !== action.key) {
@@ -325,7 +366,7 @@ export default (routeConfigs, stackConfig = {}) => {
}
// Handle replace action
if (action.type === NavigationActions.REPLACE) {
if (action.type === StackActions.REPLACE) {
const routeIndex = state.routes.findIndex(r => r.key === action.key);
// Only replace if the key matches one of our routes
if (routeIndex !== -1) {
@@ -351,7 +392,7 @@ export default (routeConfigs, stackConfig = {}) => {
// Update transitioning state
if (
action.type === NavigationActions.COMPLETE_TRANSITION &&
action.type === StackActions.COMPLETE_TRANSITION &&
(action.key == null || action.key === state.key) &&
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
if (action.key != null && action.key != state.key) {
// Deliberately use != instead of !== so we can match null with
@@ -418,11 +459,11 @@ export default (routeConfigs, stackConfig = {}) => {
if (
action.type === NavigationActions.BACK ||
action.type === NavigationActions.POP
action.type === StackActions.POP
) {
const { key, n, immediate } = action;
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
// back from state.index, as if it were a normal "BACK" action
backRouteIndex = Math.max(1, state.index - n + 1);

View File

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

View File

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

View File

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

View File

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

View File

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