Last active
August 29, 2015 13:56
-
-
Save myndzi/9026516 to your computer and use it in GitHub Desktop.
React state update propagation helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Call initially with arguments as a path from this.state | |
* to the key you want a callback to update: | |
* | |
* this will return a function to update this.state.foo: | |
* this.update('foo') | |
* | |
* and this will return one to update this.state[0].bar: | |
* this.update('state', 0, 'bar') | |
* | |
* All arguments are optional; this returns a callback | |
* that will directly update this.state: | |
* | |
* this.update() | |
* | |
* The callback can be called with an optional further path | |
* and a value to update the state at that path. The following | |
* code blocks accomplish the same thing: | |
* | |
* var callback = this.update('foo'); | |
* callback('value'); | |
* | |
* this.setState({ foo: 'value' }) | |
* | |
* The new state object is a deep copy of this.state. | |
* You can optionally supply a further path when setting the | |
* value with the callback. the following code blocks are | |
* equivalent: | |
* | |
* var callback = this.update('foo'); | |
* callback('bar', 'value'); | |
* | |
* var newVal = { foo: { bar: 'value' } } | |
* this.setState($.extend(true, {}, this.state, newVal)) | |
* | |
* callbacks can be extended later by calling the 'callback' | |
* method. 'foobar' and 'bar' are equivalent here: | |
* | |
* var foo = this.update('foo'), | |
* foobar = foo.callback('bar'); | |
* | |
* var bar = this.update('foo', 'bar'); | |
* | |
* Extending callbacks and specifying multiple arguments for | |
* paths are essentially equivalent. These all do the same thing: | |
* | |
* this.update('foo').callback('bar').callback('baz')('value'); | |
* this.update('foo', 'bar')('baz', 'value'); | |
* this.update('foo', 'bar', 'baz')('value'); | |
* this.update().callback('foo', 'bar', 'baz')('value'); | |
* | |
*/ | |
function updateHelper(/* keys */) { | |
var i = 0, x = arguments.length, args = new Array(x); | |
for (;i < x; i++) { args[i] = arguments[i]; } | |
var newState = $.extend(true, { }, this.state), | |
noKey = { }; | |
return createCallback.apply(this, [newState, newState].concat(args)); | |
// state always points to a copy of the root state | |
// ptr points to the current subkey. we also hold | |
// onto the last key value so we can assign to the | |
// object reference given by ptr | |
function createCallback(state, ptr/*, args...*/) { | |
var i = 0, x = arguments.length, | |
state = arguments[i++], | |
ptr = arguments[i++], | |
key; | |
for (; i < x-1; i++) { | |
if (arguments[i] === noKey) { | |
continue; | |
} else { | |
ptr = ptr[arguments[i]]; | |
} | |
} | |
var key = arguments[i++]; | |
if (typeof key === 'undefined') { key = noKey; } | |
// no scope variables this time, new bindings every call | |
var cb = updateState.bind(this, state, ptr, key); | |
cb.callback = createCallback.bind(this, state, ptr, key); | |
return cb; | |
} | |
function updateState(state, ptr/*, args...*/) { | |
var i = 0, x = arguments.length, | |
state = arguments[i++], | |
ptr = arguments[i++]; | |
for (; i < x-2; i++) { ptr = ptr[arguments[i]]; } | |
var key = arguments[i++], | |
val = arguments[i++]; | |
if (typeof val === 'undefined') { | |
// never got a 'key' | |
state = key; | |
} else { | |
ptr[key] = val; | |
} | |
return this.setState(state); | |
} | |
} | |
'use strict'; | |
var assert = require('assert'), | |
$ = { extend: require('jquery-extend') }; | |
var origState = { | |
foo: [ | |
{ bar: 'a' }, | |
{ bar: 'b' } | |
], | |
keke: 'lar', | |
a: { b: { c: 'd' } } | |
}, origJson = JSON.stringify(origState); | |
function cmp(a, b) { return JSON.stringify(a) === JSON.stringify(b); } | |
var test = { | |
state: origState, | |
update: updateHelper, | |
setState: function (newState) { return newState; } | |
}; | |
var res = test.update('keke')('hai'); | |
assert(res !== origState); | |
assert(res.keke === 'hai'); | |
var pre = test.update('keke'), res; | |
res = pre('lar'); | |
assert(cmp(res, origState)); | |
res = pre('lar'); | |
assert(cmp(res, origState)); | |
var res = test.update('foo', 0)('bar', 'unf'); | |
assert(res.foo[0].bar === 'unf'); | |
var res = test.update('foo').callback(0, 'bar')('unf'); | |
assert(res.foo[0].bar === 'unf'); | |
assert(res.foo.length === 2); | |
assert(res.keke === 'lar'); | |
var res = test.update('a').callback('b').callback('c')('d'); | |
assert(cmp(res, origState)); | |
var res = test.update('a', 'b')('c', 'd'); | |
assert(cmp(res, origState)); | |
var res = test.update('a', 'b', 'c')('d'); | |
assert(cmp(res, origState)); | |
var res = test.update().callback('a', 'b', 'c')('d'); | |
assert(cmp(res, origState)); | |
console.log('done'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
And a rather more compact version of the above for use in a react component: