Source: app/reducers/nonSerializableReducer.js

import handleActions from 'redux-actions/lib/handleActions';
import { createSelector } from 'reselect';
import {
  CLEAR_NON_SERIALIZABLE,
  REGISTER_NON_SERIALIZABLE,
} from '../actions/nonSerializableActions';

/**
 * Map of non-serializable state. Only accessed in this file and by nonSerializableActions.js.
 * Should not be used directly.
 * @type {object}
 */
export const nonSerializableMap = {};

const nonSerializableNamespaceReducer = handleActions(
  {
    [REGISTER_NON_SERIALIZABLE]: (state, { payload: { key, id } }) => ({
      ...state,
      [key]: id,
    }),
    [CLEAR_NON_SERIALIZABLE]: (state, { payload: { key } }) => {
      if (state[key]) {
        delete nonSerializableMap[state[key]];
      }

      const { [key]: toDelete, ...newState } = state; // eslint-disable-line no-unused-vars

      return newState;
    },
  },
  {},
);

/**
 * Reducer to store global non-serializable per-user state.
 *
 * Use this to store global non-serializable state that should **not** be shared between
 * different users on a Node.JS node. If the state can be shared between users (like an instance
 * of a utility class) use the `injector.js` logic instead.
 *
 * This state **will not** be shared between server and client. When the state is serialized
 * to send to the client, this state will be stripped. If you want an instance to be available
 * on the server and the client, you will need to register a separate instance with this
 * reducer on both sides.
 */
const nonSerializableReducer = (
  state = {
    namespaces: {},
  },
  action,
) => {
  switch (action.type) {
    case REGISTER_NON_SERIALIZABLE:
      return {
        ...state,
        namespaces: {
          ...state.namespaces,
          [action.payload.namespace]: nonSerializableNamespaceReducer(
            state.namespaces[action.payload.namespace],
            action,
          ),
        },
      };
    case CLEAR_NON_SERIALIZABLE:
      return {
        ...state,
        namespaces: {
          ...state.namespaces,
          [action.payload.namespace]: nonSerializableNamespaceReducer(
            state.namespaces[action.payload.namespace],
            action,
          ),
        },
      };
    default:
      return state;
  }
};

/**
 * Returns a new selector that returns looks up a piece of non serializable state
 * @param {string} [namespace='default'] The namespace where the state should be looked
 * up. Only required if one was given in `registerNonSerializable`
 * @example const promiseSelector = makeNonSerializableSelector('promises');
 * const myPromise = promiseSelector(state.nonSerializable, { key: 'getHeaderContent' });
 */
export const makeNonSerializableSelector = (namespace = 'default') =>
  createSelector(
    (state, { key }) => state.namespaces[namespace] && state.namespaces[namespace][key],
    id => id && nonSerializableMap[id],
  );

export default nonSerializableReducer;