# react-kiss **Repository Path**: mirrors_ecomfe/react-kiss ## Basic Information - **Project Name**: react-kiss - **Description**: A simple and stupid react container solution - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-08 - **Last Updated**: 2026-01-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # react-kiss [Redux](https://redux.js.org/) and [mobx-state-tree](https://github.com/mobxjs/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 ```shell 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: ```javascript const setCurrentUser = (user, state) => { if (state.currentUser) { return {}; } return { currentUser: user }; }; ``` or a state updater function like: ```javascript const addValue = (amount, state) => { if (state.value >= 100) { return {}; } return ({value}) => ({value: value + amount}); }; ``` #### 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: ```javascript function* saveTodo(todo) { yield {submitting: true}; try { const newTodo = yield postTodo(todo); yield state => { const {todos} = state; return { todos: [...todos, newTodo], submitting: false }; }; } catch (ex) { yield {submitting: false, error: ex}; } }; ``` This generator function receives `(payload, getState)` as its arguments, and yields value in 3 types: - a simple object is treated as a state patch. - a function is treated as a state updater. - a `Promise` instance is treated as an async process, its resolved value or rejected error will returned back to `yield` expression. ### 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: ```javascript const selectors = { filterVisibleTodos({todos, filter}) { return filter ? todos.filter(todo => todo.includes(filter)) : todos; } }; ``` 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: ```javascript import {defineRegion} from 'react-kiss'; const initialState = { todos: [ 'Buy milk', 'Meet John at peace park' ], filter: '', error: null, submitting: false }; const workflows = { * saveTodo(todo) { yield {submitting: true}; try { const newTodo = yield postTodo(todo); yield state => { const {todos} = state; return { todos: [...todos, newTodo], submitting: false }; }; } catch (ex) { yield {submitting: false, error: ex}; } }, filterByKeyword(keyword) { return {filter: keyword}; } }; const selectors = { filterVisibleTodos({todos, filter}) { return filter ? todos.filter(todo => todo.includes(filter)) : todos; } }; const todoRegion = defineRegion(initialState, workflows, selectors); export const establishTodo = todoRegion.establish; export const joinTodo = todoRegion.join; ``` 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: ```jsx import {establishTodo} from 'regions'; const Todo = () => (
); export default establishTodo('Todo')(Todo); ``` 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: ```jsx import {PureComponent} from 'react'; import {bind} from 'lodash-decorators'; import {joinTodo} from 'regions'; class AddTodo extends PureComponent { state = { todoText: '' }; @bind() syncTodoText(e) { this.setState({todoText: e.target.value}); } @bind() async saveTodo() { const {todoText} = this.state; const {onSaveTodo} = this.props; await onSaveTodo(todoText); this.setState({todoText: ''}); } componentDidUpdate(prevProps) { if (this.props.error !== prevProps.error) { alert(this.props.error.message); // eslint-disable-line no-alert } } render() { const {todoText} = this.state; const {submitting} = this.props; return (
); } } const mapToProps = ({submitting, error, saveTodo}) => ({submitting, error, onSaveTodo: saveTodo}); export default joinTodo(mapToProps)(AddTodo); ``` This is very similar to react-redux's `connect` function except it only requires one `mapToProps` function. ## Combine regions We can establish region at any place, it is also straightforward to establish multiple regions with different types: ```javascript import {compose} from 'recompose'; import {establishTodo, establishNote} from 'regions'; const App = () => ( // ... ); const enhance = compose( establishTodo('MyTodo'), establishNote('Note') ); export default ehnahce(App); ``` 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: ```jsx import {compose} from 'recompose'; import {joinAll} from 'react-kiss'; import {establishNote, joinNote, joinGlobal} from 'regions'; const Note = ({username, visible, message, onToggle}) => (
{visible &&

{message} @ {username}

}
); const mapToProps = (note, global) => { const message = note.notes[global.username]; return { username: global.username, message: message, visible: note.visible, onToggle: note.toggle }; }; const enhance = compose( establishNote('Note'), joinAll(joinNote, joinGlobal, mapToProps) ); export default enhance(Note); ``` 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. ```jsx 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}) => (
{value}
); 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: ```javascript { 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.