A Monadic Reactive Composable State Wrapper for React Components
npm install react-most --save
<script src="https://cdn.rawgit.com/reactive-react/react-most/master/dist/vendor.js"></script>
<!-- vendor.js includes react, most, most-subject -->
<script src="https://cdn.rawgit.com/reactive-react/react-most/master/dist/react-most.js"></script>
then you can use Most.default
and Most.connect
react-most
is a simple, 100 LOC Higher Order Component for React. Its only dependencies are most, most-subject, ramda, React, and (optionally, if you prefered) RxJS.
Data flow in react-most
is simple and unidirectional.
-
Action: an
Action
can create anIntent
and send it to theIntent Stream
-
Intent Stream: a timeline of every
Intent
created by everyAction
-
Sink: a timeline of transformations of state, e.g.
--- (currentState => nextState) -- (currentState => nextState) --->
-
State: a React component's 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 Examples
- simple API
- Best Practices
- 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 either{type: 'inc'}
or{type:'dec'}
toIntent Stream
upon being called. - A counter reactively generates a state transformation function when it receives an
Intent
of either typeinc
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')
);
Inspired by Redux and Functional Reactive Programming, react-most
allows you to model user events, actions, and data as reactive streams. Now you can map, filter, compose, and join those streams to form your application's state.
In imperatively written code, you describe step-by-step how to process data. With react-most
, we simply define data transformations, then compose them to form our data flow. There are no variables, no intermediate state, and no side effects in your data flow's data composition!
In Redux, reducers' use of switch
statements can make them difficult to compose. Unlike reducers, sinks are reusable observable object.
Wrapper is simply a function and easily composable.
const countBy1 = connect(...)
const countBy2 = connect(...)
const Counter = countBy1(countBy2(CounterView))
// or
const counterable = compose(countBy1, countBy2)
const Counter = counterable(CounterView)
Because UI and UI behavior are loosely coupled, you can test a React component by just passing it data. Behaviors can be tested by calling actions and then verifying the state.
import {stateHistoryOf, Engine } from 'react-most-spec';
let counterWrapper = TestUtils.renderIntoDocument(
<Most engine={Engine}>
<Counter history={true} />
</Most>
)
let counter = TestUtils.findRenderedComponentWithType(counterWrapper, Counter)
counter.actions.inc()
counter.actions.inc()
counter.actions.inc()
expect(stateHistoryOf(counter)[2].count).toBe(3)
see more details about testing at react-most-spec or todomvc example
Asynchronous functions, such as Promises, can be converted to a stream and then flat-mapped.
intent$.map(promise => most.fromPromise(promise))
.flatMap(value => /* use the results */)
Transducer is another high-performance, functional way to compose non-monadic data flows.
Writing actions as transducers can improve reusability.
Because we have all actions' streams, we can easily reproduce the actions at anytime, or get snapshot of the state's stream and going back in time.
By passing the history
parameter into the options of connect
connect(intent$=>[/* your awesome flow */], { history: true })(App)
or passing history
as a prop
<Most>
<Counter history={true}/>
</Most>
A stream with all of the state's history will be created, called historyStream
.
If you're more familiar with RxJS, it's easy to use it with react-most
in place of most
. Simply pass a prop called engine
to the Most
component.
But, I'm strongly RECOMMEND to use the default engine
most.js
, it's howreact-most
originally built for, and production ready.
import rxEngine from 'react-most/engine/rx'
<Most engine={rxEngine}>
<App />
</Most>