2015-08-02 14:54:12 +00:00
# updeep
[![NPM version][npm-image]][npm-url]
[![Build Status][travis-image]][travis-url]
[![Code Climate][codeclimate-image]][codeclimate-url]
[![Dependency Status][daviddm-image]][daviddm-url]
[![peerDependency Status][daviddm-peer-image]][daviddm-peer-url]
2015-08-02 07:32:32 +00:00
> Easily update nested frozen objects and arrays in a declarative and immutable
> manner.
2015-07-31 15:53:25 +00:00
2015-08-02 14:40:32 +00:00
2015-08-01 17:09:25 +00:00
## About
2015-08-07 04:30:55 +00:00
**NOTE: Before updeep is 1.0, method or semantics may change without a major version bump, but only if really necessary.**
2015-08-01 17:09:25 +00:00
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
2015-08-02 07:32:32 +00:00
[PureRenderMixin]). Because of this, everything returned by updeep is frozen.
2015-08-01 17:09:25 +00:00
2015-08-02 07:32:32 +00:00
updeep requires [lodash], but works very well with [lodash-fp] or [Ramda]. As a
matter of fact, many of the helpers functions are [curried][currying] [lodash]
functions with their parameters reversed (as [lodash-fp] do).
2015-08-01 17:09:25 +00:00
Note that the parameters may be backwards from what you are used to. updeep
2015-08-02 07:32:32 +00:00
supports [partial application][currying], so the parameter order is:
2015-08-05 07:27:56 +00:00
`updeep(updates, object)` .
2015-08-01 17:09:25 +00:00
2015-08-02 07:32:32 +00:00
## API and Examples
2015-08-01 17:09:25 +00:00
2015-08-01 17:26:13 +00:00
### Full example
2015-08-01 17:09:25 +00:00
```js
var u = require('updeep');
2015-08-01 17:18:10 +00:00
var person = {
2015-08-05 07:39:56 +00:00
name: { first: 'Bill', last: 'Sagat' },
children: [
{ name: 'Mary-Kate', age: 7 },
{ name: 'Ashley', age: 7 }
],
todo: [
'Be funny',
'Manage household'
],
2015-08-01 17:18:10 +00:00
email: 'bill@example.com',
version: 1
};
var inc = function(i) { return i + 1; }
2015-08-07 03:13:46 +00:00
var eq = function(x) { return function(y) { return x == y } };
2015-08-01 17:18:10 +00:00
var newPerson = u({
2015-08-05 07:39:56 +00:00
// Change first name
name: { first: 'Bob' },
// Increment all children's ages
children: u.map({ age: inc }),
// Update email
2015-08-01 17:18:10 +00:00
email: 'bob@example.com',
2015-08-05 07:39:56 +00:00
// Remove todo
2015-08-07 03:13:46 +00:00
todo: u.reject(eq('Be funny')),
2015-08-05 07:39:56 +00:00
// Increment version
2015-08-01 17:18:10 +00:00
version: inc
}, person);
// => {
2015-08-05 07:39:56 +00:00
// name: { first: 'Bob', last: 'Sagat' },
// children: [
// { name: 'Mary-Kate', age: 8 },
// { name: 'Ashley', age: 8 }
// ],
// todo: [
// 'Manage household'
// ],
// email: 'bob@example.com',
// version: 2
//}
2015-08-01 17:26:13 +00:00
```
2015-08-01 17:18:10 +00:00
2015-08-05 05:28:31 +00:00
**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))`
2015-08-07 04:40:36 +00:00
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(...)` .
2015-08-05 05:28:31 +00:00
#### Simple update
2015-08-01 17:18:10 +00:00
2015-08-01 17:26:13 +00:00
```js
2015-08-09 15:53:43 +00:00
var person = {
name: {
first: 'Jane',
last: 'West'
}
};
2015-08-10 05:03:48 +00:00
var result = u({ name: { first: 'Susan' } }, person);
2015-08-09 15:53:43 +00:00
expect(result).toEqual({ name: { first: 'Susan', last: 'West' } });
2015-08-01 17:26:13 +00:00
```
2015-08-01 17:09:25 +00:00
2015-08-05 05:28:31 +00:00
#### Multiple updates, including an array
2015-08-01 17:18:10 +00:00
2015-08-01 17:26:13 +00:00
```js
2015-08-09 15:53:43 +00:00
var person = {
name: {
first: 'Mike',
last: 'Smith'
},
scores: [12, 28]
};
var result = u({ name: { last: 'Jones' }, scores: { 1: 36 } }, person);
expect(result).toEqual({ name: { first: 'Mike', last: 'Jones' }, scores: [12, 36] });
2015-08-01 17:26:13 +00:00
```
2015-08-01 17:09:25 +00:00
2015-08-05 05:28:31 +00:00
#### Use a function
2015-08-01 17:18:10 +00:00
2015-08-01 17:26:13 +00:00
```js
2015-08-09 15:53:43 +00:00
function increment(i) { return i + 1; }
var scoreboard = {
scores: {
team1: 0,
team2: 0
}
};
var result = u({ scores: { team2: increment } }, scoreboard);
expect(result).toEqual({ scores: { team1: 0, team2: 1 } });
2015-08-01 17:26:13 +00:00
```
2015-08-01 17:09:25 +00:00
2015-08-05 05:28:31 +00:00
#### Partial application
2015-08-01 17:18:10 +00:00
2015-08-01 17:26:13 +00:00
```js
2015-08-09 15:53:43 +00:00
function increment(i) { return i + 1; }
2015-08-10 05:03:48 +00:00
var addOneYear = u({ age: increment });
var result = addOneYear({ name: 'Shannon Barnes', age: 62 });
2015-08-09 15:53:43 +00:00
expect(result).toEqual({ name: 'Shannon Barnes', age: 63 });
2015-08-01 17:26:13 +00:00
```
2015-08-01 17:09:25 +00:00
2015-08-05 05:28:31 +00:00
#### ES6 computed properties
```js
2015-08-09 15:53:43 +00:00
var key = 'age';
var result = u({ person: { [key]: 21 } }, { person: { name: 'Olivier P.', age: 20 } });
expect(result).toEqual({ person: { name: 'Olivier P.', age: 21 } });
2015-08-05 05:28:31 +00:00
```
2015-08-07 03:52:06 +00:00
### `u.updateIn(path(, value)(, object))`
2015-08-05 05:28:31 +00:00
Update a single value with a simple string or array path.
```js
2015-08-09 15:53:43 +00:00
var result = u.updateIn('bunny.color', 'brown', { bunny: { color: 'black' } });
expect(result).toEqual({ bunny: { color: 'brown' } });
2015-08-05 05:28:31 +00:00
```
```js
2015-08-09 15:53:43 +00:00
function increment(i) { return i + 1; }
var result = u.updateIn('bunny.age', increment, { bunny: { age: 2 } });
expect(result).toEqual({ bunny: { age: 3 } });
2015-08-05 05:28:31 +00:00
```
```js
2015-08-09 15:53:43 +00:00
var result = u({ pet: u.updateIn(['bunny', 'age'], 3) }, { pet: { bunny: { age: 2 } } });
expect(result).toEqual({ pet: { bunny: { age: 3 } } });
2015-08-05 05:28:31 +00:00
```
2015-08-05 06:36:40 +00:00
### `u.if(predicate(, updates)(, object))`
2015-08-09 15:53:43 +00:00
Apply `updates` if `predicate` is truthy, or if `predicate` is a function.
It evaluates to truthy when called with `object` .
2015-08-05 06:36:40 +00:00
```js
function isEven(x) { return x % 2 === 0; }
2015-08-09 15:53:43 +00:00
function increment(x) { return x + 1; }
2015-08-05 06:36:40 +00:00
2015-08-09 15:53:43 +00:00
var result = u({ value: u.if(isEven, increment) }, { value: 2 });
expect(result).toEqual({ value: 3 });
2015-08-05 06:36:40 +00:00
```
2015-08-06 06:10:16 +00:00
### `u.ifElse(predicate(, trueUpdates)(, falseUpdates)(, object))`
2015-08-09 15:53:43 +00:00
Apply `trueUpdates` if `predicate` is truthy, or if `predicate` is a function.
It evaluates to truthy when called with `object` . Otherwise, apply `falseUpdates` .
2015-08-06 06:10:16 +00:00
```js
function isEven(x) { return x % 2 === 0; }
2015-08-09 15:53:43 +00:00
function increment(x) { return x + 1; }
function decrement(x) { return x - 1; }
var result = u({ value: u.ifElse(isEven, increment, decrement) }, { value: 3 });
2015-08-06 06:10:16 +00:00
2015-08-09 15:53:43 +00:00
expect(result).toEqual({ value: 2 });
2015-08-06 06:10:16 +00:00
```
2015-08-05 07:25:34 +00:00
### `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` ,
2015-08-05 07:27:56 +00:00
which is equivalent to `u.map(u(...), object)` ).
2015-08-05 07:25:34 +00:00
```js
2015-08-09 15:53:43 +00:00
function increment(x) { return x + 1; }
var result = u({ values: u.map(increment) }, { values: [0, 1] });
expect(result).toEqual({ values: [1, 2] });
2015-08-05 07:25:34 +00:00
```
```js
2015-08-09 15:53:43 +00:00
function increment(x) { return x + 1; }
var result = u.map(increment, [0, 1, 2]);
2015-08-05 07:25:34 +00:00
2015-08-09 15:53:43 +00:00
expect(result).toEqual([1, 2, 3]);
2015-08-05 07:25:34 +00:00
```
```js
2015-08-09 15:53:43 +00:00
var result = u.map(increment, { a: 0, b: 1, c: 2 });
expect(result).toEqual({ a: 1, b: 2, c: 3 });
```
```js
function increment(x) { return x + 1; }
var result = u.map({ a: 100 }, [{ a: 0 }, { a: 1 }]);
expect(result).toEqual([{ a: 100 }, { a: 100 }]);
2015-08-05 07:25:34 +00:00
```
2015-08-05 05:28:31 +00:00
### `u.omit(predicate(, object))`
Remove properties. See [`_.omit` ](https://lodash.com/docs#omit ).
2015-08-01 17:23:10 +00:00
2015-08-01 17:26:13 +00:00
```js
2015-08-09 15:53:43 +00:00
var user = { user: { email: 'john@aol.com', username: 'john123', authToken: '1211..' } };
var result = u({ user: u.omit('authToken') }, user);
expect(result).toEqual({ user: { email: 'john@aol.com', username: 'john123' } });
2015-08-05 05:28:31 +00:00
```
```js
2015-08-10 05:10:11 +00:00
var user = {
user: {
email: 'john@aol.com',
username: 'john123',
authToken: '1211..',
SSN: 5551234
}
};
2015-08-09 15:53:43 +00:00
var result = u({ user: u.omit(['authToken', 'SSN']) }, user);
expect(result).toEqual({ user: { email: 'john@aol.com', username: 'john123' } });
2015-08-01 17:26:13 +00:00
```
2015-08-01 17:09:25 +00:00
2015-08-05 05:28:31 +00:00
### `u.reject(predicate(, object))`
Reject items from an array. See [`_.reject` ](https://lodash.com/docs#reject ).
2015-08-02 07:42:02 +00:00
```js
2015-08-07 03:48:04 +00:00
function isEven(i) { return i % 2 === 0; }
2015-08-09 15:53:43 +00:00
var result = u({ values: u.reject(isEven) }, { values: [1, 2, 3, 4] });
expect(result).toEqual({ values: [1, 3] });
2015-08-02 07:42:02 +00:00
```
2015-08-05 05:28:31 +00:00
### `u.withDefault(default(, updates)(, object))`
Like `u()` , but start with the default value if the original value is undefined.
2015-08-01 17:18:10 +00:00
2015-08-01 17:26:13 +00:00
```js
2015-08-09 15:53:43 +00:00
var result = u({ value: u.withDefault([], { 0: 3 }) }, {});
expect(result).toEqual({ value: [3] });
2015-08-01 17:26:13 +00:00
```
2015-08-01 17:09:25 +00:00
See the [tests] for more examples.
2015-07-31 15:53:25 +00:00
2015-08-07 03:48:04 +00:00
### `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 `===` .
```js
2015-08-09 15:53:43 +00:00
var result = u.is('friend.age', 22, { friend: { age: 22 } });
expect(result).toEqual(true);
2015-08-07 03:48:04 +00:00
```
```js
function isEven(i) { return i % 2 === 0; }
2015-08-09 15:53:43 +00:00
var result = u.is('friend.age', isEven, { friend: { age: 22 } });
expect(result).toEqual(true);
2015-08-07 03:48:04 +00:00
```
```js
2015-08-10 05:10:11 +00:00
var person = {
person: {
name: {
first: 'Jen',
last: 'Matthews'
}
}
};
2015-08-09 15:53:43 +00:00
2015-08-10 05:10:11 +00:00
// Update person's last name to Simpson if their first name is Jen
var result = u({
person: u.if(
u.is('name.first', 'Jen'),
u.updateIn('name.last', 'Simpson')
)
});
2015-08-09 15:53:43 +00:00
expect(result).toEqual({ person: { name: { first: 'Jen', last: 'Simpson' } } });
2015-08-07 03:48:04 +00:00
```
2015-07-31 15:53:25 +00:00
## Install
```sh
$ npm install --save updeep
```
2015-08-01 17:09:25 +00:00
Requires [lodash] as a peer dependency, so make sure you have it installed as
well.
2015-07-31 15:53:25 +00:00
2015-08-02 07:42:02 +00:00
## Configuration
If `NODE_ENV` is `"production"` , updeep will not attempt to freeze objects.
This may yield a slight performance gain.
2015-08-01 17:09:25 +00:00
## Motivation
2015-07-31 15:53:25 +00:00
2015-08-01 17:09:25 +00:00
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][immutablejs] such as debugging and
destructuring. I wanted something more powerful than [icepick] and more
composable than [React.addons.update].
2015-07-31 15:53:25 +00:00
2015-08-02 07:42:02 +00:00
If you're manipulating massive amounts of data frequently, you may want to
benchmark, as [Immutable.js][immutablejs] should be more efficient in that
case.
2015-08-01 17:09:25 +00:00
## Contributing
2015-08-01 16:17:21 +00:00
2015-08-01 17:09:25 +00:00
1. Fork it.
1. Create your feature branch (`git checkout -b my-new-feature`).
1. Run `gulp` to run tests and lint.
1. Commit your changes (`git commit -am 'Added some feature'`).
1. Push to the branch (`git push origin my-new-feature`).
1. Create new Pull Request.
2015-07-31 15:53:25 +00:00
2015-08-05 04:46:52 +00:00
## Releasing New Version
1. Login to npm, if you don't have access to the package, ask for it.
```bash
$ npm login
```
1. Make sure the build passes (best to let it pass on travis, but you can run it locally):
```bash
$ gulp
```
1. Bump the version:
```bash
$ npm version major|minor|patch
```
1. Update the `CHANGELOG.md` .
1. Add the new version and corresponding notes.
1. Add a link to the new version.
1. Update the `unreleased` link compare to be based off of the new version.
1. Publish and push:
```bash
$ npm publish
$ git push master --follow-tags
```
2015-07-31 15:53:25 +00:00
## License
2015-08-05 06:18:07 +00:00
MIT ©2015 [Substantial ](http://substantial.com )
2015-07-31 15:53:25 +00:00
2015-08-05 15:14:28 +00:00
[npm-image]: https://badge.fury.io/js/updeep.svg
2015-07-31 15:53:25 +00:00
[npm-url]: https://npmjs.org/package/updeep
2015-08-05 15:19:14 +00:00
[travis-image]: https://travis-ci.org/substantial/updeep.svg?branch=master
[travis-url]: https://travis-ci.org/substantial/updeep
[daviddm-image]: https://david-dm.org/substantial/updeep.svg?theme=shields.io
[daviddm-url]: https://david-dm.org/substantial/updeep
[daviddm-peer-image]: https://david-dm.org/substantial/updeep/peer-status.svg
[daviddm-peer-url]:https://david-dm.org/substantial/updeep#info=peerDependencies
[codeclimate-image]: https://codeclimate.com/github/substantial/updeep/badges/gpa.svg
[codeclimate-url]: https://codeclimate.com/github/substantial/updeep
2015-08-01 17:09:25 +00:00
[lodash]: http://lodash.com
2015-08-02 07:32:32 +00:00
[lodash-fp]: https://github.com/lodash/lodash-fp
[Ramda]: http://ramdajs.com/
2015-08-01 17:09:25 +00:00
[PureRenderMixin]: https://facebook.github.io/react/docs/pure-render-mixin.html
[redux]: https://github.com/gaearon/redux
[immutablejs]: https://github.com/facebook/immutable-js
[icepick]: https://github.com/aearly/icepick
[React.addons.update]: https://facebook.github.io/react/docs/update.html
2015-08-05 15:19:14 +00:00
[tests]: https://github.com/substantial/updeep/blob/master/test/index.js
2015-08-10 05:03:48 +00:00
[currying]: http://www.datchley.name/currying-vs-partial-application/