Container.js

import { connectAdvanced } from 'react-redux'
import shallowEqual from './utils/shallowEqual'
import withMapper from './mapper/withMapper'
import warning from './utils/warning'

/**
 * Connects a React component to a Redux store. Additionally to regular `connect()` method
 * Container supports an extensive mapping functionality designed to work nice with
 * [Controllers]{@link Controller}.
 *
 * Container is capable of mapping any part of a Redux state tree to the props of the
 * wrapped component. When mapping parts of the tree managed by [controllers]{@link Controller},
 * Container calls [Controller.$]{@link Controller#$} method to select the data, thus calling
 * any selector methods defined on the Controller level.
 *
 * If mapping the path exactly equal to
 * [Controller.mountPathString]{@link Controller#mountPathString}, dispatch functions are also
 * mapped by default.
 *
 * The list of mapping options:
 *
 * | Mapping | Description | Result |
 * | --- | --- | --- |
 * | `"path.*"` | Map all sub-keys at `path` to `props`. | `path.key1` to `props.key1`<br>`path.key2` to `props.key2` ... |
 * | `{ "path": "*" }` | Same as `"path.*"` | |
 * | `"path.key"` | Map the path to the prop that equal to the last key in the path. | `path.key` to `props.key` |
 * | `{ "path.key": "remapKey" }` | Map the path to the prop that is explicitly specified. | `path.key` to `props.remapKey` |
 * | `"path.dispatch*"` | Only map dispatch functions of the controller to props. | `path.dispatchFunc1` to `props.func1`<br>`path.dispatchFunc2` to `props.func2` ... |
 * | `{ "path.dispatch*": "*" }` | Same as `"path.dispatch*"` | |
 * | `{ "path.dispatch*": "funcs"}` | Map all dispatch functions of the controller to the specified prop. | `path.dispatchFunc1` to `props.funcs.func1`<br>`auth.dispatchFunc2` to `props.funcs.func2` ... |
 * | `"path.select*"` | Only map state managed by the controller to props (no dispatches). | |
 * | `"path.$"` | Same as `"path.select*"` | |
 * | `{ "path.select*": "*" }` | Same as `"path.select*"` | |
 * | `{ "path.select*": "keys" }` | Only map state managed by the controller to props (no dispatches). | `path.key1` to `props.keys.key1` <br> `path.key2` to `props.keys.key2` ... |
 * | `{ "path": (nextProps, value) => { ... } }` | Provide custom {@link AssignerFunction} to map `value` to the `nextProps`. `nextProps` object should be modified in place. Function is not expected to return anything. | |
 *
 *
 * @param {React.Component} WrappedComponent Component to connect.
 * @param {string|Object.<string, string|AssignerFunction>} mappings Any amount of mappings that should be applied
 *   when connecting Redux store to the component.
 *
 */
function Container (WrappedComponent, ...mappings) {
  function factory (dispatch) {
    let result = {}

    return (state, nextOwnProps) => {
      const { mapper } = nextOwnProps
      if (!mapper) {
        warning(`Property 'mapper' expected but not found.`)
        return nextOwnProps
      }

      let nextResult = Object.assign({ }, nextOwnProps)
      delete nextResult.mapper // Do not pass the mapper down

      nextResult = mapper(state, nextResult)

      if (!shallowEqual(result, nextResult)) { result = nextResult }

      return result
    }
  }

  return withMapper(connectAdvanced(factory)(WrappedComponent), ...mappings)
}

/**
 * Can be used in {@link Container} to add adjustment to default remapping logic.
 * @callback AssignerFunction
 * @param nextProps Dictionary of properties that wrapped component is about to get.
 *   This object should be modified in place. It doesn't include value
 * @param value Value obtained from the specific mapping.
 * @example
 * Container(MyComponent, {
 *   'books': (nextProps, books) => {
 *     nextProps.book = foo.books[nextProps.bookId]
 *     // Nothing else from books will be mapped
 *     // MyComponent will receive both book and bookId in props
 *   }
 * })
 */

export default Container