A Monadic Reactive Composable State Wrapper for React Component
npm install react-most --save
react-most
is simple and only 100 LOC React Higher Order Component. only depends on most and react.
data flow is simple and one way only
- Action: a action can create a Intent and send to
Intent Stream
- Intent Stream: a time line of all kinds of
Intent
created byAction
- Sink a time line of transforms of state e.g.
--- currentState => nextState --->
- State simply a react component state
sorry we don't have a book to document how to use react-most
, and I don't really need to, but
there's only 3 things you should notice when using react-most
, I'll explain by a simple counter app.
also you can refer to
- various of Examples
- simple API
- Best Practice
- Wiki
const CounterView = props => (
<div>
<button onClick={props.actions.dec}>-</button>
<span>{props.count}</span>
<button onClick={props.actions.inc}>+</button>
</div>
)
- a counter can have actions of
inc
anddec
, which will send a objec{type: 'inc'}
or{type:'dec'}
toIntent Stream
if it's been call - a counter reactivly generate state transform function when recieve intent of type
inc
ordec
.
const counterable = connect((intent$) => {
return {
sink$: intent$.map(intent => {
switch (intent.type) {
case 'inc':
return state => ({ count: state.count + 1 });
case 'dec':
return state => ({ count: state.count - 1 });
default:
return _ => _;
}
}),
inc: () => ({ type: 'inc' }),
dec: () => ({ type: 'dec' }),
}
})
const Counter = counterable(CounterView)
render(
<Most>
<Counter />
</Most>
, document.getElementById('app'));
Redux is awesome, but if you're big fan of Functional Reactive Programming, you would've imaged all user events, actions and data are Streams, then we can map,filter, compose, join on those streams to React state.
finstead of imperative describe what you want to do with data at certain step, we simple define data transforms and compose them to data flow. No variable, no state, no side effort at all while you composing data flow.
sinks are composable and reusable, not like reducer in redux, where switch statement are hard to break and compose.
also wrappers are simply function that you can easily compose
const countBy1 = connect(...)
const countBy2 = connect(...)
const Counter = countBy1(countBy2(CounterView))
// or
const counterable = compose(counterBy1, counterBy2)
const Counter = counterable(CounterView)
since UI and UI behaviour are loose couple, you can simply define a dump react component and test it by passing data. seperatly you can test behaviour by given actions, and verify it's state.
let {do$, historyStreamOf} = require('../test-utils')
let todolist = TestUtils.renderIntoDocument(
<Most >
<RxTodoList history={true}>
</RxTodoList>
</Most>
)
let div = TestUtils.findRenderedComponentWithType(todolist, RxTodoList)
do$([()=>div.actions.done(1),
()=>div.actions.done(2),
()=>div.actions.remove(2),
()=>div.actions.done(1)])
return historyStreamOf(div)
.take$(4)
.then(state=>
expect(state).toEqual({todos: [{id: 1, text: 5, done: false}]}))
when function is not async such as promise, simply convert it to Stream and flatMap it
intent$.map(promise=>most.fromPromise)
.flatMap(value=>dosomething)
transducer is another high perfomance functional way to compose data flow other than monadic.
writing actions in transducers improve reusablity.
since we have all action's Stream, we can easyliy reproduce all the action anytime, or get snapshot of state stream and going back in time.
you can pass history as 3rd parameter of connect
connect(intent$=>[awesome flow], {history:true})(App)
connect will populate history stream with all state history
then you get a create action to deal with historyStream.
if you're from Rx.js, it's easy to switch rx engine into react-most, by simply pass a props named engine
to Most
component
import rxEngine from 'react-most/engine/rx'
<Most engine={rxEngine}>
<App />
</Most>