Source: store.js

  1. /**
  2. * This module is here to help app to create the redux store
  3. * @module react-cmf/lib/store
  4. */
  5. import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
  6. import { enableBatching } from 'redux-batched-actions';
  7. import { nestedCombineReducers } from 'nested-combine-reducers';
  8. import thunk from 'redux-thunk';
  9. import invariant from 'invariant';
  10. import cmfReducers from './reducers';
  11. import httpMiddleware from './middlewares/http';
  12. import cmfMiddleware from './middlewares/cmf';
  13. import onError from './onError';
  14. /**
  15. * @typedef {Object} Store
  16. */
  17. const preReducers = [];
  18. const enhancers = [];
  19. const middlewares = [thunk, cmfMiddleware, onError.middleware];
  20. if (window) {
  21. // eslint-disable-next-line no-underscore-dangle
  22. if (window.__REDUX_DEVTOOLS_EXTENSION__) {
  23. // eslint-disable-next-line no-underscore-dangle
  24. enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__());
  25. } else if (window.devToolsExtension) {
  26. enhancers.push(window.devToolsExtension());
  27. }
  28. }
  29. let defaultHttpMiddlewareOverwrite = false;
  30. /**
  31. * setHttpMiddleware overwrites the default http middleware
  32. * httpMiddleware NEED to be executed before cmfMiddleware
  33. *
  34. * @param middleware a http middleware
  35. */
  36. function setHttpMiddleware(middleware) {
  37. const cmfMiddlewareIndex = middlewares.indexOf(cmfMiddleware);
  38. middlewares.splice(cmfMiddlewareIndex - 1, 0, middleware);
  39. defaultHttpMiddlewareOverwrite = true;
  40. }
  41. function addPreReducer(reducers) {
  42. if (typeof reducers === 'function') {
  43. preReducers.push(reducers);
  44. } else if (Array.isArray(reducers)) {
  45. preReducers.push(...reducers);
  46. }
  47. }
  48. function preApplyReducer(reducer) {
  49. if (preReducers.length === 0) {
  50. return reducer;
  51. }
  52. const newReducer = (state, action) => {
  53. const newState = preReducers.reduce(
  54. (accumulatedState, r) => r(accumulatedState, action),
  55. state,
  56. );
  57. return reducer(newState, action);
  58. };
  59. return newReducer;
  60. }
  61. /**
  62. * Return the CMF reducer
  63. * @param {function|Object} appReducer [description]
  64. * @return {function} [description]
  65. */
  66. function getReducer(appReducer) {
  67. let reducerObject = {};
  68. if (appReducer) {
  69. if (typeof appReducer === 'object') {
  70. reducerObject = { ...appReducer };
  71. } else if (typeof appReducer === 'function') {
  72. reducerObject = { app: appReducer };
  73. }
  74. } else {
  75. invariant(true, 'Are you sure you want to bootstrap an app without reducers ?');
  76. }
  77. if (!reducerObject.cmf) {
  78. reducerObject.cmf = cmfReducers;
  79. }
  80. return enableBatching(preApplyReducer(nestedCombineReducers(reducerObject, combineReducers)));
  81. }
  82. /**
  83. * return the array of all middleware needed for CMF to run
  84. * @param {array|function} middleware
  85. * @returns {array} of middlewares
  86. */
  87. function getMiddlewares(middleware) {
  88. if (Array.isArray(middleware)) {
  89. middleware.forEach(mid => {
  90. if (middlewares.indexOf(mid) === -1) {
  91. middlewares.push(mid);
  92. }
  93. });
  94. } else if (middleware) {
  95. middlewares.push(middleware);
  96. }
  97. if (!defaultHttpMiddlewareOverwrite) {
  98. setHttpMiddleware(httpMiddleware());
  99. }
  100. return middlewares;
  101. }
  102. /**
  103. * helper to create the store with all the things needed by CMF
  104. * the store look like this:
  105. * - root
  106. * |- app (with appReducer)
  107. * |- cmf (for the internals)
  108. *
  109. * @param {function} appReducer the reducer for your app.
  110. * @param {any} preloadedState if you want to create your state tree with initial values.
  111. * This is usefull for server side renderring
  112. * @param {function} enhancer The store enhancer
  113. * @param {Array|function} middleware redux middleware: http://redux.js.org/docs/api/applyMiddleware.html
  114. * @return {Object} The created store
  115. */
  116. function initialize(appReducer, preloadedState, enhancer, middleware) {
  117. const reducer = getReducer(appReducer);
  118. if (typeof enhancer === 'function') {
  119. enhancers.push(enhancer);
  120. }
  121. const middles = getMiddlewares(middleware);
  122. const store = compose(applyMiddleware(...middles), ...enhancers)(createStore)(
  123. reducer,
  124. preloadedState,
  125. );
  126. return store;
  127. }
  128. export default {
  129. addPreReducer,
  130. setHttpMiddleware,
  131. initialize,
  132. // for testing purepose only
  133. getReducer,
  134. getMiddlewares,
  135. };