UNPKG

redux-tree

Version:

An alternative way to compose Redux reducers

226 lines (178 loc) 10.6 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('immutable')) : typeof define === 'function' && define.amd ? define(['exports', 'immutable'], factory) : (factory((global.ReduxTree = global.ReduxTree || {}),global.Immutable)); }(this, (function (exports,immutable) { 'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var createShell = function createShell(branches) { var shell = branches.reduce(function (state, branch) { return _extends({}, state, _defineProperty({}, branch, undefined)); }, {}); var Shell = immutable.Record(shell); return new Shell(); }; var isReduxAction = function isReduxAction(type) { return type === '@@INIT' || /^@@redux\/.*$/.test(type); }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var isPlainObject = function isPlainObject(subject) { return (typeof subject === 'undefined' ? 'undefined' : _typeof(subject)) === 'object' && Object.prototype.toString.call(subject) === '[object Object]'; }; var REDUCERS_EXAMPLES = "\n Provide action handlers to the \"createLeaf\" function in one of the following ways:\n\n Option #1:\n { ACTION_TYPE: (state, action) => state }\n\n Option #2:\n {\n ACTION_TYPE: {\n leaf: ['path', 'to', 'leaf'],\n reduce: (state, action) => state,\n },\n }\n\n Option #3:\n {\n ACTION_TYPE: [\n (state, action) => state,\n\n {\n leaf: ['path', 'to', 'leaf'],\n reduce: (state, action) => state,\n },\n ],\n }\n"; var nonRecordState = function nonRecordState(state) { var stringifiedState = JSON.stringify(state); return "State must be Immutable Record. Received: " + stringifiedState; }; var undefinedState = function undefinedState(keyPath, action) { var stringifiedKeyPath = JSON.stringify(keyPath); var stringifiedAction = JSON.stringify(action); return "\n Reducer at keypath " + stringifiedKeyPath + " returned undefined.\n To ignore an action, you must explicitly return the previous state.\n If you want this reducer to hold no value, you can return null instead of undefined.\n\n Keypath: " + stringifiedKeyPath + "\n Action: " + stringifiedAction + "\n "; }; var badKeyPath = function badKeyPath(keyPath, action) { var stringifiedKeyPath = JSON.stringify(keyPath); var stringifiedAction = JSON.stringify(action); return "\n \"action." + action.type + "\" was dispatched.\n Trying to update state at keypath " + stringifiedKeyPath + " but resolved value is undefined.\n Most likely you misspelled one of the keys in the leaf keypath.\n Re-check your action handlers for this action.\n\n Keypath: " + stringifiedKeyPath + "\n Action: " + stringifiedAction + "\n "; }; var badActionHandler = function badActionHandler(actionHandler, action) { var stringifiedActionHandler = JSON.stringify(actionHandler); var stringifiedAction = JSON.stringify(action); return "\n Invalid action handler is passed for \"action." + action.type + "\".\n\n Received:\n Action: " + stringifiedAction + "\n Action handler: " + stringifiedActionHandler + "\n\n " + REDUCERS_EXAMPLES + "\n "; }; var createTree = function createTree(children) { var childrenNames = Object.keys(children); return function () { var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : createShell(childrenNames); var action = arguments[1]; // Record.isRecord method is available only in immutable@4.x // For now skipping this check if older version is used if (state && immutable.Record.isRecord && !immutable.Record.isRecord(state)) { throw new Error(nonRecordState(state)); } return state.withMutations(function (nextState) { childrenNames.forEach(function (child) { // eslint-disable-next-line no-param-reassign nextState = children[child](nextState, action, child); }); }); }; }; var createBranch = function createBranch(children) { var childrenNames = Object.keys(children); var keyPath = void 0; return function (state, action, parentKeyPath) { if (!keyPath) { keyPath = [].concat(parentKeyPath); } return state.withMutations(function (nextState) { if (isReduxAction(action.type)) { var branchState = nextState.getIn(keyPath); if (!branchState) { nextState.setIn(keyPath, createShell(childrenNames)); // Record.isRecord method is available only in immutable@4.x // For now skipping this check if older version is used } else if (immutable.Record.isRecord && !immutable.Record.isRecord(branchState)) { throw new Error(nonRecordState(branchState)); } } childrenNames.forEach(function (child) { var childKeyPath = keyPath.concat(child); // eslint-disable-next-line no-param-reassign nextState = children[child](nextState, action, childKeyPath); }); }); }; }; function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } var createLeaf = function createLeaf(initialState, reducers) { if (typeof initialState === 'undefined') { throw new Error('\n "createLeaf" recieved undefined on initialisation.\n The initial state may not be undefined.\n If you don\'t want to set a value for this reducer,\n you can use null instead of undefined.\n '); } if (reducers && !isPlainObject(reducers)) { throw new Error('\n Invalid action handlers are passed to "createLeaf" function.\n\n Received:\n arg #1: ' + JSON.stringify(initialState) + '\n arg #2: ' + JSON.stringify(reducers) + '\n\n ' + REDUCERS_EXAMPLES + '\n '); } if (process.env.NODE_ENV !== 'production') { // TODO: Validate reducers shape. } return function (state, action, keyPath) { var leafKeyPath = Array.isArray(keyPath) ? keyPath : [keyPath]; var leafState = state.getIn(leafKeyPath); return state.withMutations(function (nextState) { // If reducer is a plain function w/ the switch statement if (!reducers && typeof initialState === 'function') { var reducer = initialState; var nextLeafState = reducer(leafState, action); if (typeof nextLeafState === 'undefined') { throw new Error(undefinedState(leafKeyPath, action)); } return nextState.setIn(leafKeyPath, nextLeafState); } // On redux action if (isReduxAction(action.type)) { // Do nothing if state exists (i.e. rehydrated) if (typeof leafState !== 'undefined') return; // Otherwise set initial state return nextState.setIn(leafKeyPath, initialState); } // No reducers attached to the leaf, // it's fine as the state might be changed from another leafs if (!reducers) return; var type = action.type, payload = _objectWithoutProperties(action, ['type']); // No reaction required, exiting if (!reducers[type]) return; // If value of action type key is function -> reducing local leaf state if (typeof reducers[type] === 'function') { var _nextLeafState = reducers[type](leafState, payload); if (typeof _nextLeafState === 'undefined') { throw new Error(undefinedState(leafKeyPath, action)); } return nextState.setIn(leafKeyPath, _nextLeafState); } // If it's an object w/ keypath to leaf and reducer -> reducing leaf state at keypath if (isPlainObject(reducers[type])) { var externalLeafKeyPath = reducers[type].leaf; var externalLeafState = nextState.getIn(externalLeafKeyPath); if (typeof externalLeafState === 'undefined') { // NOTE: Might worth to check if this is exactly leaf, not a branch, at provided keypath. // But this data must be stored somewhere. Premature optimisation at this point. throw new Error(badKeyPath(externalLeafKeyPath, action)); } var nextExternalLeafState = reducers[type].reduce(externalLeafState, payload); if (typeof nextExternalLeafState === 'undefined') { throw new Error(undefinedState(externalLeafKeyPath, action)); } return nextState.setIn(externalLeafKeyPath, nextExternalLeafState); } // If it's an array -> iterating if (Array.isArray(reducers[type])) { return reducers[type].forEach(function (reducer) { // If array's item is a function -> reducing local leaf state if (typeof reducer === 'function') { var _nextLeafState2 = reducer(leafState, payload); if (typeof _nextLeafState2 === 'undefined') { throw new Error(undefinedState(leafKeyPath, action)); } return nextState.setIn(leafKeyPath, _nextLeafState2); } // Otherwise reducing state of the leaf at provided keypath var externalLeafState = nextState.getIn(reducer.leaf); if (typeof externalLeafState === 'undefined') { throw new Error(badKeyPath(reducer.leaf, action)); } var nextExternalLeafState = reducer.reduce(externalLeafState, payload); if (typeof nextExternalLeafState === 'undefined') { throw new Error(undefinedState(reducer.leaf, action)); } return nextState.setIn(reducer.leaf, nextExternalLeafState); }); } throw new Error(badActionHandler(reducers[type], action)); }); }; }; exports.createTree = createTree; exports.createBranch = createBranch; exports.createLeaf = createLeaf; Object.defineProperty(exports, '__esModule', { value: true }); })));