Redux and mobx-state-tree are both good state container solutions, however they introduces too many terminologies and bioperlates to structure a simple app.
We want something super simple and stupid, something that can define a state container everywhere at any granularity and combine them together to form a complete app. react-kiss is thus a state container which:
introduces minimal efforts to create and use states.
ships with both state and state transfer definitions, handles sync and async workflows naturally.
allows split states to different parts as small as possible and combine then together when required.
encourages establishing and joining state container in a smaller scope instead of a monotonous global state.
Install
npm install react-kiss
Region
A region is a container of a state and some workflows to manipulate state in the context of a given payload.
State
A state is a predefined structure and its current data, any plain object can be a state.
Workflow
A workflow is a process which receives a payload and manipulates current state, a workflow can manipulates state either synchronously or asychronously, it is also possible manipulates state multiple times within a workflow.
There are 2 forms of workflows.
Simple workflow
A simple workflow is a simple function that receives a payload and current state, it should return either a state patch like:
const setCurrentUser = (user, state) => {
if (state.currentUser) {
return {};
}
return {
currentUser: user
};
};
Selectors can also receive arbitary arguments, the first argument is always the currentState, rest arguments are those passed to selector on invocation.
When invoke a selector, the currentState argument is omitted (it is bound automatically), so the above selector is called just as const todos = filterVisibleTodos();
Define a region
To define a region, we just need to provide an initialState and a map of workflows to defineRegion exported function:
The return value of defineRegion function is an object containing establish and join function.
Establish a region
By defineRegion we get a region definition but it is not yet usable as a state container, we should establish it at a parent scope and join it from it’s children.
To establish a region, call establish function returned by defineRegion like an HOC:
The only argument of establish function is an optional name of region, by enhancing a component with establish, it now acts as a context’s Provider to manage the state.
Note a region can be established in different places, just like using Provider in different places, a child receives state from the closest regions of same type.
Join a region
All children components under a component enhanced with establish can choose to join this region by invoking join function returned from defineRegion, in case a component is joined to a region, it automatically receives state and workflows from region, a mapToProps function is used to select props:
Note that it is NOT OK to establish multiple regions with the same type (returned from the same defineRegion call), in such case only the latest region takes effects.
We can also join multiple regions using the joinAll exported function:
The joinAll function receives multiple join functions and a mapToProps function, the mapToProps function receives all region contexts in the order join functions are given.
Transient region
In some cases we don’t need a react’s context to hold our state and workflows, the withTransientRegion HOC defines a region only for given component, it is a useful utility to separate state management from presetation.
import {withTransientRegion} from 'react-kiss';
const initialState = {
value: 0
};
const workflows = {
increment(payload, {value}) {
return {value: value + 1};
},
decrement(payload, {value}) {
return {value: value - 1};
}
};
// The Counter component now is a pure presentational function component, state and workflows are defined in region
const Counter = ({value, increment, decrement}) => (
<div>
<button type="button" onClick={decrement}>dec</button>
<span>{value}</span>
<button type="button" onClick={increment}>inc</button>
</div>
);
export default withTransientRegion(initialState, workflows)(Counter);
Specific regions
react-kiss also provides some predefined regions to handle common scenarios.
Query
The defineQueryRegion function accepts a request function and defines a region in such structure:
{
queries: {
[stringifiedParams]: {
pendingMutex: 0, // The number of on-the-way request
params: {}, // Requesting params
response: {
data: {}, // Response of success request
error: {} // Response of fail request
}
},
...
},
request: function, // The workflow to trigger request
findQuery: function(params), // Selector to find query object by params
findReponse: function(params), // Selector to find query.response object by params
findData: function(params) // Selector to find query.response.data object by params
}
For each invocation of request workflow, a [stringifiedParams]: Query key-value pair is stored in queries state.
react-kiss
Redux and mobx-state-tree are both good state container solutions, however they introduces too many terminologies and bioperlates to structure a simple app.
We want something super simple and stupid, something that can define a state container everywhere at any granularity and combine them together to form a complete app. react-kiss is thus a state container which:
Install
Region
A region is a container of a state and some workflows to manipulate state in the context of a given payload.
State
A state is a predefined structure and its current data, any plain object can be a state.
Workflow
A workflow is a process which receives a payload and manipulates current state, a workflow can manipulates state either synchronously or asychronously, it is also possible manipulates state multiple times within a workflow.
There are 2 forms of workflows.
Simple workflow
A simple workflow is a simple function that receives a payload and current state, it should return either a state patch like:
or a state updater function like:
Composite workflow
A composite workflow is a workflow which may manipulates state multiple times or involves async process, it is defined as a generator function:
This generator function receives
(payload, getState)as its arguments, and yields value in 3 types:Promiseinstance is treated as an async process, its resolved value or rejected error will returned back toyieldexpression.Selector
A selector is a pure function which computes and selects certain values from current state, selectors are defined as an object with function values:
Selectors can also receive arbitary arguments, the first argument is always the
currentState, rest arguments are those passed to selector on invocation.When invoke a selector, the
currentStateargument is omitted (it is bound automatically), so the above selector is called just asconst todos = filterVisibleTodos();Define a region
To define a region, we just need to provide an
initialStateand a map ofworkflowstodefineRegionexported function:The return value of
defineRegionfunction is an object containingestablishandjoinfunction.Establish a region
By
defineRegionwe get a region definition but it is not yet usable as a state container, we should establish it at a parent scope and join it from it’s children.To establish a region, call
establishfunction returned bydefineRegionlike an HOC:The only argument of
establishfunction is an optional name of region, by enhancing a component withestablish, it now acts as a context’sProviderto manage the state.Note a region can be established in different places, just like using
Providerin different places, a child receives state from the closest regions of same type.Join a region
All children components under a component enhanced with
establishcan choose to join this region by invokingjoinfunction returned fromdefineRegion, in case a component is joined to a region, it automatically receives state and workflows from region, amapToPropsfunction is used to select props:This is very similar to react-redux’s
connectfunction except it only requires onemapToPropsfunction.Combine regions
We can establish region at any place, it is also straightforward to establish multiple regions with different types:
Note that it is NOT OK to establish multiple regions with the same type (returned from the same
defineRegioncall), in such case only the latest region takes effects.We can also join multiple regions using the
joinAllexported function:The
joinAllfunction receives multiplejoinfunctions and amapToPropsfunction, themapToPropsfunction receives all region contexts in the orderjoinfunctions are given.Transient region
In some cases we don’t need a react’s context to hold our state and workflows, the
withTransientRegionHOC defines a region only for given component, it is a useful utility to separate state management from presetation.Specific regions
react-kissalso provides some predefined regions to handle common scenarios.Query
The
defineQueryRegionfunction accepts a request function and defines a region in such structure:For each invocation of
requestworkflow, a[stringifiedParams]: Querykey-value pair is stored inqueriesstate.