Skip to content

Commit

Permalink
Add a child -> parent -> child constraint example
Browse files Browse the repository at this point in the history
all the communications...
  • Loading branch information
uniphil committed May 31, 2016
1 parent d8ece52 commit 3b6f692
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 3 deletions.
68 changes: 68 additions & 0 deletions examples/constrained-counters/Counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { Record } from 'immutable';
import { Union, Maybe } from 'results';
import { component, Update } from '../../spindle';


const Model = Record({
value: 0,
min: -Infinity,
max: Infinity,
});


const Msg = Union({
Increment: null,
Decrement: null,
});


// this is just a helper, it's not a special funciton
const constrain = model => {
const { value, min, max } = model.toObject();
return model.set('value', Math.max(min, Math.min(max, value)));
}


const handleProps = ({ min = -Infinity, max = Infinity }, model) =>
Update({ model: constrain(model.merge({ min, max })) });


const update = (msg, model) => Msg.match(msg, {
Increment: () => {
const newModel = constrain(model.update('value', v => v + 1));
return Update({
model: newModel,
emit: newModel.get('value'),
});
},

Decrement: () => {
const newModel = constrain(model.update('value', v => v - 1));
return Update({
model: newModel,
emit: newModel.get('value'),
});
},
});


const view = (model, BoundMsg) => (
<p>
<button
disabled={model.get('value') <= model.get('min')}
onClick={BoundMsg.Decrement}>
-
</button>
{model.get('value')}
<button
disabled={model.get('value') >= model.get('max')}
onClick={BoundMsg.Increment}>
+
</button>
</p>
);


export default component('Counter',
{ Model, Msg, handleProps, update, view });
50 changes: 50 additions & 0 deletions examples/constrained-counters/Parent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { Record } from 'immutable';
import { Union } from 'results';
import { component, Update } from '../../spindle';
import Counter from './Counter';


const Model = Record({
min: 0,
max: 0,
});


const Msg = Union({
SetMin: null,
SetMax: null,
});


const update = (msg, model) => Msg.match(msg, {
SetMin: v =>
Update({ model: model.set('min', v) }),

SetMax: v =>
Update({ model: model.set('max', v) }),
});


const view = (model, BoundMsg) => (
<div>
<p>min</p>
<Counter
onEmit={BoundMsg.SetMin}
max={model.get('max')} />

<p>val</p>
<Counter
min={model.get('min')}
max={model.get('max')} />

<p>max</p>
<Counter
onEmit={BoundMsg.SetMax}
min={model.get('min')} />
</div>
);


export default component('Parent',
{ Model, Msg, update, view });
6 changes: 6 additions & 0 deletions examples/constrained-counters/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Parent from './Parent';


ReactDOM.render(<Parent />, document.getElementById('app'));
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"watch-counter": "watchify examples/counter/app.js -d -t babelify --outfile bundle.js",
"watch-pair": "watchify examples/pair-of-counters/app.js -d -t babelify --outfile bundle.js",
"watch-n": "watchify examples/n-counters/app.js -d -t babelify --outfile bundle.js",
"watch-sum": "watchify examples/sum-counters/app.js -d -t babelify --outfile bundle.js"
"watch-sum": "watchify examples/sum-counters/app.js -d -t babelify --outfile bundle.js",
"watch-constrained": "watchify examples/constrained-counters/app.js -d -t babelify --outfile bundle.js"
},
"author": "uniphil",
"license": "GPL",
Expand Down
21 changes: 19 additions & 2 deletions spindle.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ const bindMsg = (Msg, update, c) =>
.reduce((a, b) => Object.assign(a, b), {});


const propsEq = (a, b) => {
for (const k in a) {
if (a[k] !== b[k] && k !== 'onEmit' && k !== 'children') return false;
}
const aCount = Object.keys(a).length - !!a.onEmit - !!a.children;
const bCount = Object.keys(b).length - !!b.onEmit - !!b.children;
return aCount === bCount;
};


export const Cmd = Immutable.Record({
run: null,
abort: null,
Expand Down Expand Up @@ -89,6 +99,7 @@ const createSpindle = () => {
export function component(name, {
Model = Immutable.Record({}),
Msg = Union({}),
handleProps = (_, model) => Update({ model }),
update,
view,
subscriptions = () => [],
Expand All @@ -111,13 +122,19 @@ export function component(name, {
this._isSpindleRoot = false;
}
this.getSpindle().register(this);
this.run(Update({ model: Model() }));
this.run(handleProps(this.props, Model()));
}

getChildContext() {
return { spindle: this.context.spindle || this._spindle };
}

componentWillReceiveProps(nextProps) {
if (!propsEq(this.props, nextProps)) {
this.run(handleProps(nextProps, this.state.model));
}
}

shouldComponentUpdate(_, nextState) {
return !Immutable.is(nextState.model, this.state.model);
}
Expand All @@ -139,7 +156,7 @@ export function component(name, {
const { model, cmds, emit } = update.toObject();
model && this.setState({ model });
cmds && this.getSpindle().pushCmds(this, cmds);
emit && this.props.onEmit && this.props.onEmit(emit);
typeof emit !== 'undefined' && this.props.onEmit && this.props.onEmit(emit);
}

render() {
Expand Down

0 comments on commit 3b6f692

Please sign in to comment.