diff --git a/examples/constrained-counters/Counter.js b/examples/constrained-counters/Counter.js
new file mode 100644
index 0000000..73065f7
--- /dev/null
+++ b/examples/constrained-counters/Counter.js
@@ -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) => (
+
+
+ {model.get('value')}
+
+
+);
+
+
+export default component('Counter',
+ { Model, Msg, handleProps, update, view });
diff --git a/examples/constrained-counters/Parent.js b/examples/constrained-counters/Parent.js
new file mode 100644
index 0000000..5efdd96
--- /dev/null
+++ b/examples/constrained-counters/Parent.js
@@ -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) => (
+
+
min
+
+
+
val
+
+
+
max
+
+
+);
+
+
+export default component('Parent',
+ { Model, Msg, update, view });
diff --git a/examples/constrained-counters/app.js b/examples/constrained-counters/app.js
new file mode 100644
index 0000000..3475db0
--- /dev/null
+++ b/examples/constrained-counters/app.js
@@ -0,0 +1,6 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Parent from './Parent';
+
+
+ReactDOM.render(, document.getElementById('app'));
diff --git a/package.json b/package.json
index 9ae7789..3c2ca21 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/spindle.js b/spindle.js
index 058aee5..8892075 100644
--- a/spindle.js
+++ b/spindle.js
@@ -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,
@@ -89,6 +99,7 @@ const createSpindle = () => {
export function component(name, {
Model = Immutable.Record({}),
Msg = Union({}),
+ handleProps = (_, model) => Update({ model }),
update,
view,
subscriptions = () => [],
@@ -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);
}
@@ -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() {