npm package: @smalldots/create-container
Description: Predictable state containers for React apps
Latest version: 9.1.0

# Create Container

[![npm version](https://badge.fury.io/js/%40smalldots%2Fcreate-container.svg)](https://badge.fury.io/js/%40smalldots%2Fcreate-container)

Predictable state [containers](<https://en.wikipedia.org/wiki/Container_(abstract_data_type)>) for React apps

## Philosophy

Redux is awesome, but sometimes it might not be the best fit. MobX is awesome, but sometimes it might not be the best fit.

Wouldn't it be great if you could choose a different state management architecture for the different features you are building?

For example, for a simple theme/language store Redux would be overkill, but it shines for stores that receives data through a set of websocket events.

That's the reason we've built this library, you can create multiple stores ([containers](<https://en.wikipedia.org/wiki/Container_(abstract_data_type)>)) having the option to choose the best architecture for the job.

## Installation

```
npm install --save @smalldots/create-container
```

### Import Cost

![Import Cost](https://d.pr/i/TC9k6Z+)

## Demos

- Simple counter: https://codesandbox.io/s/03znoqjz4w
- Static and dynamic forms: https://codesandbox.io/s/n79lpw1l1p
- Decoupling state, behavior and presentation: https://codesandbox.io/s/6lwyx2p69k
- Container composition: https://codesandbox.io/s/pp8494k03q
- Starting mutations when mounting: https://codesandbox.io/s/n0nv2vo21l
- Entity container: https://codesandbox.io/s/mzyljzlxj8
- Using high order containers to create multiple counters: https://codesandbox.io/s/yqrxnk48x1
- Advanced example (data fetching + entities handling): https://codesandbox.io/s/1vn8j7yq83

## Infinity Architecture (optional)

You have the choice to use the simplest [container](<https://en.wikipedia.org/wiki/Container_(abstract_data_type)>) version, `createContainer`, the Redux inspired version, `createReduxContainer` or `createInfinityContainer`, which implements the Infinity Architecture:

![Infinity Architecture Preview](https://cl.ly/1h0q3Z002v3A/Infinity%20Container.png)

## How to start mutations when mounting?

It's easy. You can play with this example: https://codesandbox.io/s/n0nv2vo21l

```js
import React from "react"
import { render } from "react-dom"

import { createContainer, Component } from "@smalldots/create-container"

const CounterContainer = createContainer({
  initialState: { count: 0 },
  mutations: {
    increment: () => state => ({ count: state.count + 1 }),
    decrement: () => state => ({ count: state.count - 1 })
  },
  effects: {
    incrementForever: () => ({ increment }) => setInterval(increment, 1000),
    maybeDecrement: () => ({ decrement }) => Math.random() <= 0.2 && decrement()
  }
})

const App = () => (
  <CounterContainer.Provider>
    <CounterContainer.Consumer>
      {({ count, incrementForever, maybeDecrement }) => (
        <Component onMount={incrementForever} onUpdate={maybeDecrement}>
          <p>
            Current count: {count}
            <br />
            <small>
              (It has a 20% chance of pausing for a while, you are not crazy :D)
            </small>
          </p>
        </Component>
      )}
    </CounterContainer.Consumer>
  </CounterContainer.Provider>
)

render(<App />, document.getElementById("root"))
```

## How to compose multiple containers?

It's easy. You can play with this example: https://codesandbox.io/s/pp8494k03q

```js
import React, { Fragment } from "react"
import { render } from "react-dom"

import { createContainer, Compose } from "@smalldots/create-container"

const CounterContainer = createContainer({
  initialState: { count: 0 },
  mutations: {
    increment: () => state => ({ count: state.count + 1 }),
    decrement: () => state => ({ count: state.count - 1 })
  }
})

const TodoContainer = createContainer({
  initialState: { todos: [] },
  mutations: {
    addTodo: () => state => ({
      todos: [
        { id: `${new Date().getTime()}.${Math.ceil(Math.random() * 1000)}` },
        ...state.todos
      ]
    })
  }
})

const App = () => (
  <Compose
    components={[<CounterContainer.Provider />, <TodoContainer.Provider />]}
  >
    <Compose
      components={[<CounterContainer.Consumer />, <TodoContainer.Consumer />]}
    >
      {({ count, increment, decrement }, { todos, addTodo }) => (
        <Fragment>
          <p>Current count: {count}</p>
          <button onClick={increment}>Increment</button>
          <button onClick={decrement}>Decrement</button>
          <hr />
          <button onClick={addTodo}>Add Todo</button>
          <pre>{JSON.stringify(todos, null, 2)}</pre>
        </Fragment>
      )}
    </Compose>
  </Compose>
)

render(<App />, document.getElementById("root"))
```

## Usage

```js
import React, { Fragment } from "react"
import { render } from "react-dom"

import {
  createContainer,
  createReduxContainer,
  createInfinityContainer,
  Compose
} from "@smalldots/create-container"

const CounterContainer = createContainer({
  initialState: { count: 0 },
  selectors: {
    getNextCount: state => state.count + 1
  },
  mutations: {
    increment: () => state => ({ count: state.count + 1 }),
    decrement: () => state => ({ count: state.count - 1 })
  },
  effects: {
    gamify: () => ({ increment, decrement, setState }) =>
      setTimeout(() => {
        Math.random() > 0.5 ? increment() : decrement()
        setState(state => ({ count: state.count + Math.random() }))
      }, 500)
  }
})

const ReduxCounterContainer = createReduxContainer({
  initialState: { count: 0 },
  reducer: {
    INCREMENT: state => ({ count: state.count + 1 }),
    DECREMENT: state => ({ count: state.count - 1 }),
    RANDOMIZE: state => ({ count: state.count + Math.random() })
  },
  selectors: {
    getNextCount: state => state.count + 1
  },
  actions: {
    increment: "INCREMENT",
    decrement: "DECREMENT",
    randomize: "RANDOMIZE"
  },
  effects: {
    gamify: () => ({ increment, decrement, randomize }) =>
      setTimeout(() => {
        Math.random() > 0.5 ? increment() : decrement()
        randomize()
      }, 500)
  }
})

const InfinityCounterContainer = createInfinityContainer({
  initialState: { count: 0 },
  reducer: {
    INCREMENT: state => ({ count: state.count + 1 }),
    DECREMENT: state => ({ count: state.count - 1 }),
    RANDOMIZE: state => ({ count: state.count + Math.random() })
  },
  selectors: {
    getCount: state => state.count,
    getNextCount: state => state.count + 1
  },
  events: {
    incrementClicked: "INCREMENT_CLICKED",
    decrementClicked: "DECREMENT_CLICKED",
    gamifyClicked: "GAMIFY_CLICKED"
  },
  listeners: {
    INCREMENT_CLICKED: ({ increment }) => increment(),
    DECREMENT_CLICKED: ({ decrement }) => decrement(),
    GAMIFY_CLICKED: ({ gamify }) => gamify()
  },
  commands: {
    increment: "INCREMENT",
    decrement: "DECREMENT",
    randomize: "RANDOMIZE"
  },
  effects: {
    gamify: () => ({ increment, decrement, randomize }) =>
      setTimeout(() => {
        Math.random() > 0.5 ? increment() : decrement()
        randomize()
      }, 500)
  }
})

const Counter = ({ count, nextCount, onIncrement, onDecrement, onGamify }) => (
  <Fragment>
    <p>Current count: {count}</p>
    <p>If you click "Increment" the next count will be: {nextCount}</p>
    <button onClick={onIncrement}>Increment</button>
    <button onClick={onDecrement}>Decrement</button>
    <button onClick={onGamify}>Gamify</button>
  </Fragment>
)

const CounterContainerDemo = () => (
  <CounterContainer.Consumer>
    {({ count, getNextCount, increment, decrement, gamify }) => (
      <Counter
        count={count}
        nextCount={getNextCount()}
        onIncrement={increment}
        onDecrement={decrement}
        onGamify={gamify}
      />
    )}
  </CounterContainer.Consumer>
)

const ReduxCounterContainerDemo = () => (
  <ReduxCounterContainer.Consumer>
    {({ count, getNextCount, increment, decrement, gamify }) => (
      <Counter
        count={count}
        nextCount={getNextCount()}
        onIncrement={increment}
        onDecrement={decrement}
        onGamify={gamify}
      />
    )}
  </ReduxCounterContainer.Consumer>
)

const InfinityCounterContainerDemo = () => (
  <InfinityCounterContainer.Consumer>
    {({
      getCount,
      getNextCount,
      incrementClicked,
      decrementClicked,
      gamifyClicked
    }) => (
      <Counter
        count={getCount()}
        nextCount={getNextCount()}
        onIncrement={incrementClicked}
        onDecrement={decrementClicked}
        onGamify={gamifyClicked}
      />
    )}
  </InfinityCounterContainer.Consumer>
)

const App = () => (
  <Compose
    components={[
      <CounterContainer.Provider />,
      <ReduxCounterContainer.Provider />,
      <InfinityCounterContainer.Provider />
    ]}
  >
    <h1>Container Demo</h1>
    <CounterContainerDemo />
    <h1>Redux Container Demo</h1>
    <ReduxCounterContainerDemo />
    <h1>Infinity Container Demo</h1>
    <InfinityCounterContainerDemo />
  </Compose>
)

render(<App />, document.getElementById("root"))
```