A simple (non-feature complete) clone reimplementation of Redux built using React 16.3's createContext
.
import React, { Fragment } from 'react'
import createStore from 'react-state-reducer'
const INITIAL_STATE = {
count: 0,
}
const counterReducer = action => (
state = INITIAL_STATE,
) => {
switch (action.type) {
case 'INC':
return { count: state.count + 1 }
case 'DEC':
return { count: state.count - 1 }
default:
return state
}
}
const { Provider, Consumer } = createStore(counterReducer)
export default () => (
<Provider>
<Consumer>
{({ count, dispatch }) => (
<Fragment>
<button onClick={() => dispatch({ type: 'DEC' })}>
-
</button>
{count}
<button onClick={() => dispatch({ type: 'INC' })}>
+
</button>
</Fragment>
)}
</Consumer>
</Provider>
)
There are two core parts of react-state-reducer's API that should be kept in mind.
The first is the reducer
. A reducer
is a higher order function (meaning it returns a function), that first accepts an action
and returns a function that accepts the current state (and optionally any props provided to the <Provider>
). This returned function should then handle returning the updated state.
That was a lot of different words describing what this is, lets look at some code to walk through this.
- Lets start with the higher order function:
const myReducer = someAction => {
return (state) => {
...
}
}
This function accepts a single argument (someAction
) and returns a function. Which in turn accepts a single argument called state
. This is often written as the following:
const myReducer = action => state => { ... };
- Handling updates within the returned function:
const myReducer = action => state => {
if (action === 'SOME_UPDATE') {
return {
...state,
someKey: '🆒',
}
}
}
Some notes with this implementation above:
-
It doesn't follow the generic Redux reducer concept of using a
switch
you can handle writing this function however you would like (usingswitch
, or using an if/else) -
Right now we are implicitly returning
undefined
ifaction
does not equal'SOME_UPDATE'
, while this is valid, you should make sure to at least handle returning the initial state if the action is the following shape:
{type: '@@INIT', payload: null}
What this looks like in practice:
const todoReducer = action => (state = { todos: [] }) => {
switch (action.type) {
case 'ADD_TODO': {
return {
todos: [
...state.todos,
{
id: state.todos.length,
completed: false,
text: action.payload,
},
],
}
}
case 'CHECK_TODO': {
return {
todos: todos.map(todo => {
if (todo.id === action.payload) {
return {
...todo,
completed: !todo.completed,
}
} else {
return todo
}
}),
}
}
default:
return state
}
}
createStore
is the main export of react-state-reducer, it is a function that accepts a reducer
as an argument. It calls the reducer with the initialization action:
yourReducer({ type: '@@INIT', payload: null })(null)
and then uses the result of calling the reducer as the initial state for the store. createStore
then returns an object with the following:
{
Provider: Node,
Consumer: Node
}
The Provider component exposes two props, the first is children
which can be a single component, or many components.
The second prop is onUpdate
which is a function that will be called after each state update happens, it is called with the updated state.
<Provider
onUpdate={newState => {
/* Maybe sync state to localStorage, or something else */
}}
>
<App />
</Provider>
The Consumer component has one core prop, children
, children is a function that gets called with an object with the following keys:
dispatch
- top level keys from state
For example if your state shape looks like this:
{
todos: [],
text: ''
}
the consumer children function would look like the following:
<Consumer>
{({
dispatch,
todos,
text
}) => (
...
)}
</Consumer>
Consumer also optionally supports a selector
prop, which can either take in an array of functions or a single function to select your slice of state you care about:
<Consumer selector={s => s.todos}>
{({ dispatch }, todos) => {
/* */
}}
</Consumer>
This library is highly influenced by all the great work put into Redux and React-Redux. For some more information on the differences between this library and Redux see redux.