9.5 KiB
updeep
Easily update nested frozen objects and arrays in a declarative and immutable manner.
About
NOTE: Before updeep is 1.0, method or semantics may change without a major version bump, but only if really necessary.
Updating deeply nested objects/arrays is a bit of a pain. updeep makes it painless by allowing you to declare the updates you would like to make and it will take care of the rest. It will recursively return the same instance if no changes have been made, making it ideal for using reference equality checks to detect changes (like PureRenderMixin). Because of this, everything returned by updeep is frozen.
updeep requires lodash, but works very well with lodash-fp or Ramda. As a matter of fact, many of the helpers functions are curried lodash functions with their parameters reversed (as lodash-fp do).
Note that the parameters may be backwards from what you are used to. updeep
supports partial application, so the parameter order is:
updeep(updates, object)
.
API and Examples
Full example
var u = require('updeep');
var person = {
name: { first: 'Bill', last: 'Sagat' },
children: [
{ name: 'Mary-Kate', age: 7 },
{ name: 'Ashley', age: 7 }
],
todo: [
'Be funny',
'Manage household'
],
email: 'bill@example.com',
version: 1
};
var inc = function(i) { return i + 1; }
var eq = function(x) { return function(y) { return x == y } };
var newPerson = u({
// Change first name
name: { first: 'Bob' },
// Increment all children's ages
children: u.map({ age: inc }),
// Update email
email: 'bob@example.com',
// Remove todo
todo: u.reject(eq('Be funny')),
// Increment version
version: inc
}, person);
// => {
// name: { first: 'Bob', last: 'Sagat' },
// children: [
// { name: 'Mary-Kate', age: 8 },
// { name: 'Ashley', age: 8 }
// ],
// todo: [
// 'Manage household'
// ],
// email: 'bob@example.com',
// version: 2
//}
NOTE: All functions are curried, so if you see f(x(, y))
, it can be called with either f(x, y)
or f(x)(y)
.
u(updates(, object))
Update as many values as you want, as deeply as you want. The updates
parameter can either be an object, a function, or a value. Everything returned from u
is frozen recursively.
If updates
is an object, for each key/value, it will apply the updates specified in the value to object[key]
.
If updates
is a function, it will call the function with object
and return the value.
If updates
is a value, it will return that value.
Sometimes, you may want to set an entire object to a property, or a function. In that case, you'll need to use a function to return that value, otherwise it would be interpreted as an update. Ex. function() { return { a: 0 }; }
.
Also available at u.update(...)
.
Simple update
u({ x: { b: 3 } }, { x: { a: 0, b: 0 } });
// => { x: { a: 0, b: 3 } }
Multiple updates, including an array
u({ x: { b: 3 }, y: { 1: 4 } }, { x: { a: 0, b: 0 }, y: [0, 0] });
// => { x: { a: 0, b: 3 }, y: [0, 4] }
Use a function
function inc(i) { return i + 1; }
u({ x: { b: inc } }, { x: { a: 0, b: 0 } });
// => { x: { a: 0, b: 1 } }
Partial application
var setBTo3 = u({ b: 3 });
setBTo3({ a: 0, b: 0 });
// => { a: 0, b: 3 })
ES6 computed properties
var key = 'b';
u({ x: { [key]: 3 } }, { x: { a: 0, b: 0 } });
// => { x: { a: 0, b: 3 } }
u.updateIn(path(, value)(, object))
Update a single value with a simple string or array path.
u.updateIn('a.b', 3, { a: { b: 0 } });
// => { a: { b: 3 } };
function inc(i) { return i + 1; }
u.updateIn('a.b', inc, { a: { b: 0 } });
// => { a: { b: 1 } };
u({
x: u.updateIn(['a', 'b'], 3)
}, { x: { a: { b: 0 } } });
// => { x: { a: { b: 3 } } };
u.if(predicate(, updates)(, object))
Apply updates
only if predicate
is truthy or, if predicate
is a function,
it evaluates to truthy when called with object
.
var object = { a: 2 };
function isEven(x) { return x % 2 === 0; }
function inc(x) { return x + 1; }
u({
a: u.if(isEven, inc),
}, object);
// => { a: 3 }
u.ifElse(predicate(, trueUpdates)(, falseUpdates)(, object))
Apply trueUpdates
if predicate
is truthy or, if predicate
is a function,
it evaluates to truthy when called with object
. Otherwise, apply falseUpdates
.
var object = { a: 3 };
function isEven(x) { return x % 2 === 0; }
function inc(x) { return x + 1; }
function dec(x) { return x - 1; }
u({
a: u.if(isEven, inc),
}, object);
// => { a: 2 }
u.map(iteratee(, object))
If iteratee is a function, map it over the values in object
.
If it is an object, apply it as updates to each value in object
,
which is equivalent to u.map(u(...), object)
).
function inc(x) { return x + 1; }
u({
a: u.map(inc)
}, { a: [0, 1] });
// => { a: [1, 2] }
function inc(x) { return x + 1; }
u.map(inc, [0, 1, 2]);
// => [1, 2, 3]
u.map(inc, { a: 0, b: 1, c: 2});
// => { a: 1, b: 2, c: 3}
u.map({ a: 2 }, [{ a: 0 }, { a: 1 }]);
// => [{ a: 2 }, { a: 2 }]
u.omit(predicate(, object))
Remove properties. See _.omit
.
u({ x: u.omit('b') }, { x: { a: 0, b: 0, c: 0 } });
// => { x: { a: 0, c: 0 } }
u({ x: u.omit(['b', 'c']) }, { x: { a: 0, b: 0, c: 0 } });
// => { x: { a: 0 } }
u.reject(predicate(, object))
Reject items from an array. See _.reject
.
function isEven(i) { return i % 2 === 0; }
u({ x: u.reject(isEven) }, { x: [1, 2, 3, 4] });
// => { x: [1, 3] }
u.withDefault(default(, updates)(, object))
Like u()
, but start with the default value if the original value is undefined.
u({ x: u.withDefault([], { 0: 3 }) }, {});
// => { x: [3] }
See the tests for more examples.
u.is(path(, predicate)(, object))
Returns true
if the predicate
matches the path
applied to the object
.
If the predicate
is a function, the result is returned. If not, they are compared with ===
.
u.is('a.b', 4, { a: { b: 4 } });
// => true
function isEven(i) { return i % 2 === 0; }
u.is('a.b', isEven, { a: { b: 4 } });
// => true
u({
person: u.if(u.is('name.first', 'Jen'), u.updateIn('name.last', 'Simpson'))
}, { person: { name: { first: 'Jen', last: 'Matthews' } } });
// => { person: { name: { first: 'Jen', last: 'Simpson' } } }
Install
$ npm install --save updeep
Requires lodash as a peer dependency, so make sure you have it installed as well.
Configuration
If NODE_ENV
is "production"
, updeep will not attempt to freeze objects.
This may yield a slight performance gain.
Motivation
While creating reducers for use with redux, I wanted something that made it easy to work with frozen objects. Native javascript objects have some nice advantages over things like Immutable.js such as debugging and destructuring. I wanted something more powerful than icepick and more composable than React.addons.update.
If you're manipulating massive amounts of data frequently, you may want to benchmark, as Immutable.js should be more efficient in that case.
Contributing
- Fork it.
- Create your feature branch (
git checkout -b my-new-feature
). - Run
gulp
to run tests and lint. - Commit your changes (
git commit -am 'Added some feature'
). - Push to the branch (
git push origin my-new-feature
). - Create new Pull Request.
Releasing New Version
-
Login to npm, if you don't have access to the package, ask for it.
$ npm login
-
Make sure the build passes (best to let it pass on travis, but you can run it locally):
$ gulp
-
Bump the version:
$ npm version major|minor|patch
-
Update the
CHANGELOG.md
. -
Add the new version and corresponding notes.
-
Add a link to the new version.
-
Update the
unreleased
link compare to be based off of the new version. -
Publish and push:
$ npm publish $ git push master --follow-tags
License
MIT ©2015 Substantial