Skip to content

Commit eea3a62

Browse files
committed
composable wrapper
1 parent 566ef91 commit eea3a62

File tree

3 files changed

+116
-53
lines changed

3 files changed

+116
-53
lines changed

lib/__tests__/react-most-test.jsx

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@ import React from 'react';
22
import ReactDOM from 'react-dom';
33
import TestUtils from 'react-addons-test-utils';
44
import * as most from 'most';
5+
import {compose} from 'ramda';
56
import Most, {connect} from '../react-most';
6-
import {do$, historyStreamOf, intentStreamOf} from '../test-utils'
7-
8-
const CounterView = props=> (
9-
<div>
10-
<span className="count">{props.count}</span>
11-
<span className="wrapperProps">{props.wrapperProps}</span>
12-
<span className="overwritedProps">{props.overwritedProps}</span>
13-
<span className="backward" onClick={props.history.backward}>-</span>
14-
<span className="forward" onClick={props.history.forward}>+</span>
15-
</div>
16-
)
7+
import {do$, historyStreamOf, intentStreamOf,dispatch} from '../test-utils'
8+
9+
const CounterView = React.createClass({
10+
render(){
11+
return (
12+
<div>
13+
<span className="count">{this.props.count}</span>
14+
<span className="wrapperProps">{this.props.wrapperProps}</span>
15+
<span className="overwritedProps">{this.props.overwritedProps}</span>
16+
<span className="backward" onClick={this.props.history.backward}>-</span>
17+
<span className="forward" onClick={this.props.history.forward}>+</span>
18+
</div>
19+
)
20+
}
21+
})
1722

1823
CounterView.defaultProps = {count: 0, overwritedProps: 'inner'}
1924

20-
const Counter = connect(intent$=>{
25+
const counterWrapper = connect(intent$=>{
2126
return {
2227
sink$: intent$.map(intent=>{
2328
switch(intent.type) {
@@ -39,7 +44,9 @@ const Counter = connect(intent$=>{
3944
changeWrapperProps: (value)=>({type:'changeWrapperProps', value}),
4045
changeDefaultProps: (value)=>({type:'changeDefaultProps', value}),
4146
}
42-
})(CounterView)
47+
})
48+
49+
const Counter = counterWrapper(CounterView)
4350

4451
describe('react-most', () => {
4552
describe('actions', ()=>{
@@ -174,4 +181,40 @@ describe('react-most', () => {
174181
})
175182
})
176183
})
184+
185+
describe('composable', ()=>{
186+
const counterWrapper2 = connect(intent$=>{
187+
return {
188+
sink2$: intent$.map(intent=>{
189+
switch(intent.type) {
190+
case 'inc2':
191+
return state=>({count:state.count+2})
192+
case 'dec2':
193+
return state=>({count:state.count-2})
194+
default:
195+
return state=>state
196+
}
197+
}),
198+
inc2: ()=>({type:'inc2'}),
199+
dec2: ()=>({type:'dec2'}),
200+
}
201+
})
202+
let counterWrapper21 = compose(counterWrapper2, counterWrapper)
203+
const Counter2 = counterWrapper21(CounterView)
204+
it('counter add inc2, dec2', ()=>{
205+
let counterWrapper = TestUtils.renderIntoDocument(
206+
<Most >
207+
<Counter2 history={true} />
208+
</Most>
209+
)
210+
let counter = TestUtils.findRenderedComponentWithType(counterWrapper, Counter2)
211+
dispatch([{type:'inc'},
212+
{type: 'inc2'},
213+
{type:'dec'}], counter)
214+
return historyStreamOf(counter)
215+
.take$(3)
216+
.then(state=>expect(state.count).toEqual(2))
217+
})
218+
})
219+
177220
})

lib/react-most.js

Lines changed: 58 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,60 @@ import mergeAll from 'ramda/src/mergeAll'
55
import pick from 'ramda/src/pick'
66
import keys from 'ramda/src/keys'
77
// unfortunately React doesn't support symbol as context key yet, so let me just preteding using Symbol until react implement the Symbol version of Object.assign
8-
const intentStream = "__reactive.react.intentStream__";
9-
const historyStream = "__reactive.react.historyStream__";
10-
const flatObserve = "__reactive.react.flatObserve__";
8+
const INTENT_STREAM = "__reactive.react.intentStream__";
9+
const HISTORY_STREAM = "__reactive.react.historyStream__";
10+
const EACH_FLATMAP = "__reactive.react.flatObserve__";
1111

1212
const CONTEXT_TYPE = {
13-
[intentStream]: React.PropTypes.object,
14-
[historyStream]: React.PropTypes.object,
15-
[flatObserve]: React.PropTypes.func,
16-
}
17-
18-
function observable(obj){
19-
return !!obj.subscribe
13+
[INTENT_STREAM]: React.PropTypes.object,
14+
[HISTORY_STREAM]: React.PropTypes.object,
15+
[EACH_FLATMAP]: React.PropTypes.func,
2016
}
2117

2218
export function connect(main, initprops={}) {
2319
return function(ReactClass){
20+
if (ReactClass.contextTypes === CONTEXT_TYPE) {
21+
class Connect extends React.PureComponent {
22+
constructor(props, context) {
23+
super(props, context);
24+
let [actions, sink$] = actionsAndSinks(main(context[INTENT_STREAM],props), this)
25+
this.sink$ = sink$.concat(props.sink$||[])
26+
this.actions = mergeAll([actions, props.actions])
27+
}
28+
render(){
29+
return <ReactClass {...this.props} {...initprops} sink$={this.sink$} actions={this.actions}/>
30+
}
31+
}
32+
Connect.contextTypes = CONTEXT_TYPE
33+
return Connect
34+
}
2435
class Connect extends React.PureComponent {
2536
constructor(props, context) {
2637
super(props, context);
27-
let _actions = {
28-
fromEvent(e, f=x=>x){
29-
context[intentStream].send(f(e));
30-
},
31-
fromPromise(p){
32-
p.then(context[intentStream].send);
33-
}
34-
};
35-
let sinks = main(context[intentStream],props);
36-
let _actionsSinks = []
3738
if(initprops.history || props.history){
38-
initprops.history = initHistory(context[historyStream])
39+
initprops.history = initHistory(context[HISTORY_STREAM])
3940
initprops.history.travel.observe(state=>{
4041
return this.setState(state)
4142
})
4243
}
43-
for(let name in sinks){
44-
if(observable(sinks[name])){
45-
_actionsSinks.push(sinks[name]);
46-
}
47-
else if(sinks[name] instanceof Function){
48-
_actions[name] = (...args)=>{
49-
return this.context[intentStream].send(sinks[name].apply(this, args));
50-
}
51-
}
52-
}
53-
this.actionsSinks = _actionsSinks;
44+
45+
let [actions, sink$] = actionsAndSinks(main(context[INTENT_STREAM],props), this)
46+
this.sink$ = sink$.concat(props.sink$||[])
47+
this.actions = mergeAll([actions, props.actions])
5448
let defaultKey = keys(ReactClass.defaultProps)
5549
this.state = mergeAll([ReactClass.defaultProps, pick(defaultKey, props)])
56-
this.actions = mergeAll([_actions, props.actions])
5750
}
5851
componentWillReceiveProps(nextProps){
5952
this.setState(state=>pick(keys(state), nextProps))
6053
}
6154
componentDidMount(){
62-
this.context[flatObserve](this.actionsSinks, (action)=>{
55+
this.context[EACH_FLATMAP](this.sink$, (action)=>{
6356
if(action instanceof Function) {
6457
this.setState((prevState, props)=>{
6558
let newState = action.call(this, prevState,props);
6659
if(initprops.history && newState != prevState){
6760
initprops.history.cursor = -1;
68-
this.context[historyStream].send(prevState);
61+
this.context[HISTORY_STREAM].send(prevState);
6962
}
7063
return newState;
7164
});
@@ -101,9 +94,9 @@ let Most = React.createClass({
10194
}
10295

10396
return {
104-
[intentStream]: engine.intentStream,
105-
[flatObserve]: engine.flatObserve,
106-
[historyStream]: engine.historyStream,
97+
[INTENT_STREAM]: engine.intentStream,
98+
[EACH_FLATMAP]: engine.flatObserve,
99+
[HISTORY_STREAM]: engine.historyStream,
107100
}
108101
},
109102
render(){
@@ -112,3 +105,30 @@ let Most = React.createClass({
112105
});
113106

114107
export default Most;
108+
109+
function observable(obj){
110+
return !!obj.subscribe
111+
}
112+
113+
function actionsAndSinks(sinks, self){
114+
let _sinks = [];
115+
let _actions = {
116+
fromEvent(e, f=x=>x){
117+
self.context[INTENT_STREAM].send(f(e));
118+
},
119+
fromPromise(p){
120+
p.then(self.context[INTENT_STREAM].send);
121+
}
122+
};
123+
for(let name in sinks){
124+
if(observable(sinks[name])){
125+
_sinks.push(sinks[name]);
126+
}
127+
else if(sinks[name] instanceof Function){
128+
_actions[name] = (...args)=>{
129+
return self.context[INTENT_STREAM].send(sinks[name].apply(self, args));
130+
}
131+
}
132+
}
133+
return [_actions, _sinks]
134+
}

lib/test-utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ function do$(listOfActions) {
1818
return from(listOfActions).forEach(x=>x())
1919
}
2020

21-
function sendTo(listOfIntent, component) {
21+
function dispatch(listOfIntent, component) {
2222
let s = intentStreamOf(component)
2323
return from(listOfIntent).forEach(i=>s.send(i))
2424
}
2525

26-
export {do$, historyStreamOf, intentStreamOf}
26+
export {do$, historyStreamOf, intentStreamOf, dispatch}

0 commit comments

Comments
 (0)