@smalldots/create-container
v9.1.0
Published
Predictable state containers for React apps
Downloads
81
Maintainers
Readme
Create Container
Predictable state containers 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) having the option to choose the best architecture for the job.
Installation
npm install --save @smalldots/create-container
Import Cost
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 version, createContainer
, the Redux inspired version, createReduxContainer
or createInfinityContainer
, which implements the Infinity Architecture:
How to start mutations when mounting?
It's easy. You can play with this example: https://codesandbox.io/s/n0nv2vo21l
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
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
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"))