From 3a8b50ad550b176bbce5713c5c74e1318c10f2a0 Mon Sep 17 00:00:00 2001 From: Hedger Wang Date: Wed, 8 Jun 2016 11:33:45 -0700 Subject: [PATCH] Kill NavigationReducers Summary: For navigation actions at high level, reducers from NavigationReducers does not know anything about the app-specific state thus people won't use these reducers. Instead, people should build their own reducers. There are a lot of good libraries available that help people to reducing things if that's what they really need. At the low level, for navigation state changes that don't involve app-specific state, `NavigationStateUtils` should server that kind of need. `NavigationReducers` serves little benefit cause it does not know the app state, it does not know how to traverse the navigation states which can be a tree, a list or a map. That said, we hold no interest in owning in the core navigation library. Reviewed By: ericvicenti Differential Revision: D3372910 fbshipit-source-id: 797382b46e7d64b7ad578b51dd37e2b941faa83d --- .../NavigationCardStack-example.js | 2 +- .../UIExplorer/UIExplorerNavigationReducer.js | 57 ++++++- .../NavigationExperimental.js | 2 - .../Reducer/NavigationFindReducer.js | 41 ----- .../Reducer/NavigationReducer.js | 24 --- .../Reducer/NavigationStackReducer.js | 102 ----------- .../Reducer/NavigationTabsReducer.js | 103 ------------ .../__tests__/NavigationFindReducer-test.js | 45 ----- .../__tests__/NavigationStackReducer-test.js | 158 ------------------ .../__tests__/NavigationTabsReducer-test.js | 56 ------- 10 files changed, 52 insertions(+), 538 deletions(-) delete mode 100644 Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js delete mode 100644 Libraries/NavigationExperimental/Reducer/NavigationReducer.js delete mode 100644 Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js delete mode 100644 Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js delete mode 100644 Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js delete mode 100644 Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js delete mode 100644 Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js diff --git a/Examples/UIExplorer/NavigationExperimental/NavigationCardStack-example.js b/Examples/UIExplorer/NavigationExperimental/NavigationCardStack-example.js index c54cc1ba9e9..6116f04df3a 100644 --- a/Examples/UIExplorer/NavigationExperimental/NavigationCardStack-example.js +++ b/Examples/UIExplorer/NavigationExperimental/NavigationCardStack-example.js @@ -80,7 +80,7 @@ class YourApplication extends React.Component { switch (type) { case 'push': // push a new route. - const route = {key: Date.now()}; + const route = {key: 'route-' + Date.now()}; navigationState = NavigationStateUtils.push(navigationState, route); break; diff --git a/Examples/UIExplorer/UIExplorerNavigationReducer.js b/Examples/UIExplorer/UIExplorerNavigationReducer.js index cec32ee91ba..741c6abb78c 100644 --- a/Examples/UIExplorer/UIExplorerNavigationReducer.js +++ b/Examples/UIExplorer/UIExplorerNavigationReducer.js @@ -22,27 +22,72 @@ */ 'use strict'; -const React = require('react'); const ReactNative = require('react-native'); // $FlowFixMe : This is a platform-forked component, and flow seems to only run on iOS? const UIExplorerList = require('./UIExplorerList'); + const { NavigationExperimental, } = ReactNative; + + const { - Reducer: NavigationReducer, + StateUtils: NavigationStateUtils, } = NavigationExperimental; -const StackReducer = NavigationReducer.StackReducer; import type {NavigationState} from 'NavigationTypeDefinition'; -import type {UIExplorerAction} from './UIExplorerActions'; - export type UIExplorerNavigationState = { externalExample: ?string; stack: NavigationState; }; +const defaultGetReducerForState = (initialState) => (state) => state || initialState; + +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); + if (!lastParentState) { + return lastState; + } + + const activeSubState = lastParentState.routes[lastParentState.index]; + const activeSubReducer = getReducerForStateWithDefault(activeSubState); + const nextActiveState = activeSubReducer(activeSubState, action); + if (nextActiveState !== activeSubState) { + const nextChildren = [...lastParentState.routes]; + nextChildren[lastParentState.index] = nextActiveState; + return { + ...lastParentState, + routes: nextChildren, + }; + } + + const subReducerToPush = getPushedReducerForAction(action, lastParentState); + if (subReducerToPush) { + return NavigationStateUtils.push( + lastParentState, + subReducerToPush(null, action) + ); + } + + switch (action.type) { + case 'back': + case 'BackAction': + if (lastParentState.index === 0 || lastParentState.routes.length === 1) { + return lastParentState; + } + return NavigationStateUtils.pop(lastParentState); + } + + return lastParentState; + }; +} + const UIExplorerStackReducer = StackReducer({ getPushedReducerForAction: (action, lastState) => { if (action.type === 'UIExplorerExampleAction' && UIExplorerList.Modules[action.openExample]) { @@ -106,7 +151,7 @@ function UIExplorerNavigationReducer(lastState: ?UIExplorerNavigationState, acti return { externalExample: null, stack: newStack, - } + }; } return lastState; } diff --git a/Libraries/NavigationExperimental/NavigationExperimental.js b/Libraries/NavigationExperimental/NavigationExperimental.js index ff8179189fd..4548b336098 100644 --- a/Libraries/NavigationExperimental/NavigationExperimental.js +++ b/Libraries/NavigationExperimental/NavigationExperimental.js @@ -16,14 +16,12 @@ const NavigationCard = require('NavigationCard'); const NavigationCardStack = require('NavigationCardStack'); const NavigationHeader = require('NavigationHeader'); const NavigationPropTypes = require('NavigationPropTypes'); -const NavigationReducer = require('NavigationReducer'); const NavigationStateUtils = require('NavigationStateUtils'); const NavigationTransitioner = require('NavigationTransitioner'); const NavigationExperimental = { // Core StateUtils: NavigationStateUtils, - Reducer: NavigationReducer, // Views AnimatedView: NavigationAnimatedView, diff --git a/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js deleted file mode 100644 index fd9e74bda9a..00000000000 --- a/Libraries/NavigationExperimental/Reducer/NavigationFindReducer.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule NavigationFindReducer - * @flow - */ -'use strict'; - -/* - * NavigationFindReducer takes an array of reducers, and returns a reducer that - * iterates through all of the reducers and the result of the first reducer - * that modifies the input - */ - -import type { - NavigationRoute, - NavigationReducer -} from 'NavigationTypeDefinition'; - -function NavigationFindReducer( - reducers: Array, - defaultState: NavigationRoute, -): NavigationReducer { - return function(lastState: ?NavigationRoute, action: ?any): NavigationRoute { - for (let i = 0; i < reducers.length; i++) { - let reducer = reducers[i]; - let newState = reducer(lastState, action); - if (newState !== lastState) { - return newState || defaultState; - } - } - return lastState || defaultState; - }; -} - -module.exports = NavigationFindReducer; diff --git a/Libraries/NavigationExperimental/Reducer/NavigationReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationReducer.js deleted file mode 100644 index 4fb52297daa..00000000000 --- a/Libraries/NavigationExperimental/Reducer/NavigationReducer.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule NavigationReducer - * @flow - */ -'use strict'; - -var NavigationFindReducer = require('NavigationFindReducer'); -var NavigationStackReducer = require('NavigationStackReducer'); -var NavigationTabsReducer = require('NavigationTabsReducer'); - -const NavigationReducer = { - FindReducer: NavigationFindReducer, - StackReducer: NavigationStackReducer, - TabsReducer: NavigationTabsReducer, -}; - -module.exports = NavigationReducer; diff --git a/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js deleted file mode 100644 index 9c884d55eab..00000000000 --- a/Libraries/NavigationExperimental/Reducer/NavigationStackReducer.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule NavigationStackReducer - * @flow-broken - */ -'use strict'; - -const NavigationStateUtils = require('NavigationStateUtils'); - -import type { - NavigationRoute, - NavigationState, - NavigationReducer, -} from 'NavigationTypeDefinition'; - -export type ReducerForStateHandler = (state: NavigationRoute) => NavigationReducer; - -export type PushedReducerForActionHandler = (action: any, lastState: NavigationState) => ?NavigationReducer; - -export type StackReducerConfig = { - /* - * The initialState is that the reducer will use when there is no previous state. - * Must be a NavigationState: - * - * { - * routes: [ - * {key: 'subState0'}, - * {key: 'subState1'}, - * ], - * index: 0, - * key: 'navStackKey' - * } - */ - initialState: NavigationState; - - /* - * Returns the sub-reducer for a particular state to handle. This will be called - * when we need to handle an action on a sub-state. If no reducer is returned, - * no action will be taken - */ - getReducerForState?: ReducerForStateHandler; - - /* - * Returns a sub-reducer that will be used when pushing a new route. If a reducer - * is returned, it be called to get the new state that will be pushed - */ - getPushedReducerForAction: PushedReducerForActionHandler; -}; - -const defaultGetReducerForState = (initialState) => (state) => state || initialState; - -function NavigationStackReducer({initialState, getReducerForState, getPushedReducerForAction}: StackReducerConfig): NavigationReducer { - const getReducerForStateWithDefault = getReducerForState || defaultGetReducerForState; - return function (lastState: ?NavigationRoute, action: any): NavigationRoute { - if (!lastState) { - return initialState; - } - const lastParentState = NavigationStateUtils.getParent(lastState); - if (!lastParentState) { - return lastState; - } - - const activeSubState = lastParentState.routes[lastParentState.index]; - const activeSubReducer = getReducerForStateWithDefault(activeSubState); - const nextActiveState = activeSubReducer(activeSubState, action); - if (nextActiveState !== activeSubState) { - const nextChildren = [...lastParentState.routes]; - nextChildren[lastParentState.index] = nextActiveState; - return { - ...lastParentState, - routes: nextChildren, - }; - } - - const subReducerToPush = getPushedReducerForAction(action, lastParentState); - if (subReducerToPush) { - return NavigationStateUtils.push( - lastParentState, - subReducerToPush(null, action) - ); - } - - switch (action.type) { - case 'back': - case 'BackAction': - if (lastParentState.index === 0 || lastParentState.routes.length === 1) { - return lastParentState; - } - return NavigationStateUtils.pop(lastParentState); - } - - return lastParentState; - }; -} - -module.exports = NavigationStackReducer; diff --git a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js b/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js deleted file mode 100644 index 5e60cf86604..00000000000 --- a/Libraries/NavigationExperimental/Reducer/NavigationTabsReducer.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule NavigationTabsReducer - * @flow-broken - */ -'use strict'; - -const NavigationFindReducer = require('NavigationFindReducer'); -const NavigationStateUtils = require('NavigationStateUtils'); - -import type { - NavigationReducer, - NavigationRoute, -} from 'NavigationTypeDefinition'; - -const ActionTypes = { - JUMP_TO: 'react-native/NavigationExperimental/tabs-jumpTo', -}; - -export type JumpToAction = { - type: typeof ActionTypes.JUMP_TO, - index: number, -}; -function NavigationTabsJumpToAction(index: number): JumpToAction { - return { - type: ActionTypes.JUMP_TO, - index, - }; -} - -type TabsReducerConfig = { - key: string; - initialIndex: number; - tabReducers: Array; -}; - -function NavigationTabsReducer({key, initialIndex, tabReducers}: TabsReducerConfig): NavigationReducer { - return function(lastNavState: ?NavigationRoute, action: ?any): NavigationRoute { - if (!lastNavState) { - lastNavState = { - routes: tabReducers.map(reducer => reducer(null, null)), - index: initialIndex || 0, - key, - }; - } - const lastParentNavState = NavigationStateUtils.getParent(lastNavState); - if (!action || !lastParentNavState) { - return lastNavState; - } - if ( - action.type === ActionTypes.JUMP_TO && - action.index !== lastParentNavState.index - ) { - return NavigationStateUtils.jumpToIndex( - lastParentNavState, - action.index, - ); - } - const subReducers = tabReducers.map((tabReducer, tabIndex) => { - return function(navState: ?NavigationRoute, tabAction: any): NavigationRoute { - if (!navState) { - return lastParentNavState; - } - const parentState = NavigationStateUtils.getParent(navState); - const tabState = parentState && parentState.routes[tabIndex]; - const nextTabState = tabReducer(tabState, tabAction); - if (nextTabState && tabState !== nextTabState) { - const tabs = parentState && parentState.routes || []; - tabs[tabIndex] = nextTabState; - return { - ...lastParentNavState, - tabs, - index: tabIndex, - }; - } - return lastParentNavState; - }; - }); - let selectedTabReducer = subReducers.splice(lastParentNavState.index, 1)[0]; - subReducers.unshift(function(navState: ?NavigationRoute, action: any): NavigationRoute { - if (navState && action.type === 'BackAction') { - return NavigationStateUtils.jumpToIndex( - lastParentNavState, - initialIndex || 0 - ); - } - return lastParentNavState; - }); - subReducers.unshift(selectedTabReducer); - const findReducer = NavigationFindReducer(subReducers, lastParentNavState); - return findReducer(lastParentNavState, action); - }; -} - -NavigationTabsReducer.JumpToAction = NavigationTabsJumpToAction; - -module.exports = NavigationTabsReducer; diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js deleted file mode 100644 index d57afdfb0bd..00000000000 --- a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationFindReducer-test.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow-broken - */ -'use strict'; - -jest - .unmock('NavigationFindReducer'); - -const NavigationFindReducer = require('NavigationFindReducer'); - -describe('NavigationFindReducer', () => { - - it('handles basic find reducing with strings', () => { - let reducer = NavigationFindReducer([ - s => s, - s => s + '_yes', - s => 'nope', - ]); - let route = reducer('input'); - expect(route).toBe('input_yes'); - - reducer = NavigationFindReducer([ - (s, action) => s, - (s, action) => 'origRoute', - (s, action) => 'firstChangedState', - ]); - route = reducer('origRoute', 'action1'); - expect(route).toBe('firstChangedState'); - - reducer = NavigationFindReducer([ - (s, action) => s, - (s, action) => action, - ]); - route = reducer('inputState', 'action2'); - expect(route).toBe('action2'); - }); - -}); diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js deleted file mode 100644 index 23828086f61..00000000000 --- a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationStackReducer-test.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow-broken - */ -'use strict'; - -jest - .unmock('NavigationStackReducer') - .unmock('NavigationStateUtils'); - -jest.setMock('React', {Component() {}, PropTypes: {}}); - -const NavigationStackReducer = require('NavigationStackReducer'); -const NavigationRootContainer = require('NavigationRootContainer'); - -describe('NavigationStackReducer', () => { - - it('provides default/initial state', () => { - const initialState = { - routes: [ - {key: 'a'}, - ], - index: 0, - key: 'myStack', - }; - const reducer = NavigationStackReducer({ - getPushedReducerForAction: (action) => null, - getReducerForState: (state) => () => state, - initialState, - }); - const dummyAction = {type: 'dummyAction'}; - expect(reducer(null, dummyAction)).toBe(initialState); - }); - - it('handles basic reducer pushing', () => { - const reducer = NavigationStackReducer({ - getPushedReducerForAction: (action) => { - if (action.type === 'TestPushAction') { - return (state) => state || {key: action.testValue}; - } - return null; - }, - getReducerForState: (state) => () => state, - initialState: { - routes: [ - {key: 'first'}, - ], - index: 0, - key: 'myStack' - } - }); - const state1 = reducer(null, {type: 'default'}); - expect(state1.routes.length).toBe(1); - expect(state1.routes[0].key).toBe('first'); - expect(state1.index).toBe(0); - - const action = {type: 'TestPushAction', testValue: 'second'}; - const state2 = reducer(state1, action); - expect(state2.routes.length).toBe(2); - expect(state2.routes[0].key).toBe('first'); - expect(state2.routes[1].key).toBe('second'); - expect(state2.index).toBe(1); - }); - - it('handles BackAction', () => { - const reducer = NavigationStackReducer({ - getPushedReducerForAction: (action) => { - if (action.type === 'TestPushAction') { - return (state) => state || {key: action.testValue}; - } - return null; - }, - getReducerForState: (state) => () => state, - initialState: { - routes: [ - {key: 'a'}, - {key: 'b'}, - ], - index: 1, - key: 'myStack', - }, - }); - - const state1 = reducer(null, {type: 'MyDefaultAction'}); - expect(state1.routes[0].key).toBe('a'); - expect(state1.routes[1].key).toBe('b'); - expect(state1.routes.length).toBe(2); - expect(state1.index).toBe(1); - expect(state1.key).toBe('myStack'); - - const state2 = reducer(state1, NavigationRootContainer.getBackAction()); - expect(state2.routes[0].key).toBe('a'); - expect(state2.routes.length).toBe(1); - expect(state2.index).toBe(0); - - const state3 = reducer(state2, NavigationRootContainer.getBackAction()); - expect(state3).toBe(state2); - }); - - it('allows inner reducers to handle back actions', () => { - const subReducer = NavigationStackReducer({ - getPushedReducerForAction: () => {}, - initialState: { - routes: [ - {key: 'first'}, - {key: 'second'}, - ], - index: 1, - key: 'myInnerStack' - }, - }); - - const reducer = NavigationStackReducer({ - getPushedReducerForAction: (action) => { - if (action.type === 'TestPushAction') { - return subReducer; - } - - return null; - }, - getReducerForState: (state) => { - if (state.key === 'myInnerStack') { - return subReducer; - } - return () => state; - }, - initialState: { - routes: [ - {key: 'a'}, - ], - index: 0, - key: 'myStack' - } - }); - - const state1 = reducer(null, {type: 'MyDefaultAction'}); - const state2 = reducer(state1, {type: 'TestPushAction'}); - expect(state2.routes.length).toBe(2); - expect(state2.routes[0].key).toBe('a'); - expect(state2.routes[1].key).toBe('myInnerStack'); - expect(state2.routes[1].routes.length).toBe(2); - expect(state2.routes[1].routes[0].key).toBe('first'); - expect(state2.routes[1].routes[1].key).toBe('second'); - - const state3 = reducer(state2, NavigationRootContainer.getBackAction()); - expect(state3.routes.length).toBe(2); - expect(state3.routes[0].key).toBe('a'); - expect(state3.routes[1].key).toBe('myInnerStack'); - expect(state3.routes[1].routes.length).toBe(1); - expect(state3.routes[1].routes[0].key).toBe('first'); - }); -}); diff --git a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js b/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js deleted file mode 100644 index f97379cb738..00000000000 --- a/Libraries/NavigationExperimental/Reducer/__tests__/NavigationTabsReducer-test.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @flow-broken - */ -'use strict'; - -jest - .unmock('NavigationTabsReducer') - .unmock('NavigationFindReducer') - .unmock('NavigationStateUtils'); - -const NavigationTabsReducer = require('NavigationTabsReducer'); - -const { - JumpToAction, -} = NavigationTabsReducer; - -describe('NavigationTabsReducer', () => { - - it('handles JumpTo with index', () => { - let reducer = NavigationTabsReducer({ - tabReducers: [ - (tabState, action) => tabState || 'a', - (tabState, action) => tabState || 'b', - (tabState, action) => tabState || 'c', - ], - initialIndex: 1, - }); - - let navState = reducer(); - - expect(navState.routes[0]).toBe('a'); - expect(navState.routes[1]).toBe('b'); - expect(navState.routes[2]).toBe('c'); - expect(navState.routes.length).toBe(3); - expect(navState.index).toBe(1); - - navState = reducer( - navState, - JumpToAction(2) - ); - - expect(navState.routes[0]).toEqual('a'); - expect(navState.routes[1]).toEqual('b'); - expect(navState.routes[2]).toEqual('c'); - expect(navState.routes.length).toBe(3); - expect(navState.index).toBe(2); - }); - -});