state-tree
Advanced tools
Comparing version 0.1.6 to 0.1.7
{ | ||
"name": "state-tree", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "A state tree that handles reference updates and lets you flush a description of changes", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
var React = require('react'); | ||
function hasPath(path, changes) { | ||
function hasChanged(path, changes) { | ||
return path.split('.').reduce(function (changes, key) { | ||
@@ -22,3 +22,6 @@ return changes[key]; | ||
for (var key in paths) { | ||
if (hasPath(paths[key], changes)) { | ||
if ( | ||
(typeof paths[key] === 'object' && paths[key].hasChanged(changes)) || | ||
(hasChanged(paths[key], changes)) | ||
) { | ||
return this.forceUpdate(); | ||
@@ -32,3 +35,3 @@ } | ||
var propsToPass = Object.keys(paths || {}).reduce(function (props, key) { | ||
props[key] = tree.get(paths[key]); | ||
props[key] = typeof paths[key] === 'object' ? paths[key].get() : tree.get(paths[key]); | ||
return props | ||
@@ -35,0 +38,0 @@ }, {}) |
@@ -9,3 +9,3 @@ # state-tree (EXPERIMENTAL) | ||
- **Control changes**. I want a simple way to track the changes made to the app. By using a `state.set('some.state', 'foo')` API instead of `some.state = 'foo'` this control becomes more intuitive as you have a specific API for making changes, rather than "changing stuff all over". It also makes it a lot easier to implement tracking of any changes | ||
- **Control changes**. I want a simple way to control the changes made to the app. By using a `state.set('some.state', 'foo')` API instead of `some.state = 'foo'` this control becomes more intuitive as you have a specific API for making changes, rather than "changing stuff all over". It also makes it a lot easier to implement tracking of any changes | ||
@@ -135,2 +135,3 @@ - **Fast updates**. Immutability has benefits like being able to replay state changes, undo/redo very easily and no unwanted mutations in other parts of your code. The problem though is that immutability is slow on instantiating large datasets | ||
tree.set('foo', 'bar2'); | ||
tree.merge('foo', {something: 'cool'}); | ||
tree.unset('foo'); | ||
@@ -157,2 +158,4 @@ tree.push('list', 'something'); | ||
tree.set('list.0.foo', 'bar2'); | ||
// It returns an object representing the changes | ||
tree.flushChanges(); // { list: { 0: { foo: true } } } | ||
@@ -199,1 +202,60 @@ ``` | ||
``` | ||
### Computed | ||
You can also compute state. | ||
```js | ||
import StateTree from 'state-tree'; | ||
const tree = StateTree({ | ||
foo: 'bar' | ||
}); | ||
const myComputed = tree.computed({ | ||
foo: 'foo' // The deps, just like a component | ||
}, state => { | ||
return state.foo + '!!!'; | ||
}); | ||
myComputed.get() // "bar!!!" | ||
let changes = tree.flushChanges(); // {} | ||
myComputed.hasChanged(changes); // false | ||
tree.set('foo', 'bar2'); | ||
changes = tree.flushChanges(); // { foo: true } | ||
myComputed.hasChanged(changes); // true | ||
myComputed.get() // "bar2!!!" | ||
``` | ||
So, this seems like a lot of code to make computed work, but again, this is low level. Implemented in the HOC of React you can simply do this. | ||
```js | ||
import React from 'react'; | ||
import HOC from 'state-tree/react/HOC'; | ||
import addItem from './addItem'; | ||
import awesomeItems from './computed/awesomeItems'; | ||
function Items(props) { | ||
return ( | ||
<div> | ||
<button onClick={() => addItem({foo: 'bar'})}>Add item</button> | ||
<ul> | ||
{props.list.map((item, index) => <li key={index}>{item.foo}</li>)} | ||
</ul> | ||
</div> | ||
); | ||
} | ||
export default HOC(Items, { | ||
list: awesomeItems | ||
}) | ||
``` | ||
And *awesomeItems.js* would look like: | ||
```js | ||
import tree from './tree'; | ||
export default tree.computed({ | ||
list: 'list' // Define its deps | ||
}, state => { | ||
return state.list.filter(item => item.isAwesome); | ||
}); | ||
``` | ||
There is no magic going on here. The components will pass in the flushed "change tree" to whatever computed they have. This is what tells them to verify if an update is necessary, if not already ready to calculate a new value. |
100
src/index.js
@@ -98,26 +98,32 @@ var subscribers = []; | ||
module.exports = function (initialState) { | ||
function StateTree(initialState) { | ||
var state = setReferences(initialState, []); | ||
var changes = {}; | ||
function updateChanges(host, key, specificPath) { | ||
function update(pathArray) { | ||
return function (currentPath, key, index) { | ||
if (Array.isArray(key)) { | ||
key = key[0].indexOf(key[1]); | ||
currentPath[key] = index === pathArray.length - 1 ? true : {}; | ||
} else if (index === pathArray.length - 1 && !currentPath[key]) { | ||
currentPath[key] = true; | ||
} else if (index < pathArray.length - 1) { | ||
currentPath[key] = typeof currentPath[key] === 'object' ? currentPath[key] : {}; | ||
function updateChanges(host, key, specificPath) { | ||
function update(pathArray) { | ||
return function (currentPath, key, index) { | ||
if (Array.isArray(key)) { | ||
key = key[0].indexOf(key[1]); | ||
currentPath[key] = index === pathArray.length - 1 ? true : {}; | ||
} else if (index === pathArray.length - 1 && !currentPath[key]) { | ||
currentPath[key] = true; | ||
} else if (index < pathArray.length - 1) { | ||
currentPath[key] = typeof currentPath[key] === 'object' ? currentPath[key] : {}; | ||
} | ||
return currentPath[key]; | ||
} | ||
return currentPath[key]; | ||
} | ||
host['.referencePaths'].forEach(function (path) { | ||
var pathArray = path ? path.concat(key) : [key]; | ||
pathArray.reduce(update(pathArray), changes); | ||
}); | ||
} | ||
host['.referencePaths'].forEach(function (path) { | ||
var pathArray = path ? path.concat(key) : [key]; | ||
pathArray.reduce(update(pathArray), changes); | ||
}); | ||
} | ||
function hasChanged(path, changes) { | ||
return path.split('.').reduce(function (changes, key) { | ||
return changes[key]; | ||
}, changes); | ||
} | ||
return { | ||
@@ -192,2 +198,22 @@ get: function (path) { | ||
}, | ||
merge: function () { | ||
var path; | ||
var value; | ||
if (arguments.length === 1) { | ||
path = ''; | ||
value = arguments[0]; | ||
} else { | ||
path = arguments[0]; | ||
value = arguments[1]; | ||
} | ||
var pathArray = path.split('.'); | ||
var key = pathArray.pop(); | ||
var host = getByPath(pathArray, state); | ||
var child = host[key] || host; | ||
Object.keys(value).forEach(function (mergeKey) { | ||
cleanReferences(child[mergeKey], state, key ? path.split('.').concat(mergeKey) : [mergeKey]); | ||
child[mergeKey] = setReferences(value[mergeKey], key ? pathArray.concat(key, mergeKey) : [mergeKey]); | ||
updateChanges(child, mergeKey, path); | ||
}); | ||
}, | ||
subscribe: function (cb) { | ||
@@ -206,4 +232,44 @@ subscribers.push(cb); | ||
return flushedChanges; | ||
}, | ||
computed: function (deps, cb) { | ||
var computedHasChanged = true; | ||
var value = null; | ||
return { | ||
get: function (changes) { | ||
if (computedHasChanged) { | ||
computedHasChanged = false; | ||
value = cb(Object.keys(deps).reduce(function (props, key) { | ||
var path = deps[key].split('.'); | ||
props[key] = getByPath(path, state); | ||
return props; | ||
}, {})); | ||
return value; | ||
} else { | ||
return value; | ||
} | ||
}, | ||
// Can optimize by remembering the changes in case multiple | ||
// components checks the computed, but very unlikely | ||
hasChanged: function (changes) { | ||
if (computedHasChanged) { | ||
return true; | ||
} | ||
for (var key in deps) { | ||
if (hasChanged(deps[key], changes)) { | ||
computedHasChanged = true; | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
/* | ||
- Create computed | ||
- ComponentWillMount, subscribe to computed | ||
*/ | ||
module.exports = StateTree; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32706
18
795
258