Fork of updeep using Remeda
 
 
Go to file
Aaron Jensen 98a2037f8a Update changelog 2015-09-11 12:00:02 -07:00
lib Use identity with u.if's false condition 2015-09-11 11:57:39 -07:00
perf Update curry benchmark 2015-08-12 21:19:09 -07:00
test Use identity with u.if's false condition 2015-09-11 11:57:39 -07:00
.babelrc Refactor update.js and remove some lodash 2015-08-08 23:35:53 -07:00
.editorconfig Initial commit w/ yo node 2015-07-31 08:53:25 -07:00
.eslintignore Add .eslintignore 2015-08-04 03:54:02 -07:00
.eslintrc use airbnb's eslint 2015-08-01 23:25:08 -07:00
.gitattributes Initial commit w/ yo node 2015-07-31 08:53:25 -07:00
.gitignore Initial commit w/ yo node 2015-07-31 08:53:25 -07:00
.jshintrc add jshintrc for codeclimate 2015-08-02 07:48:51 -07:00
.travis.yml Update travis notification settings 2015-08-01 09:28:52 -07:00
CHANGELOG.md Update changelog 2015-09-11 12:00:02 -07:00
LICENSE.txt Update copyright 2015-08-04 23:19:46 -07:00
README.md Update README.md 2015-08-30 17:10:45 -07:00
createWebpackConfig.js Eek out some file size 2015-08-22 11:03:38 -07:00
gulpfile.js Speed up eslint 2015-08-19 00:31:06 -07:00
karma.conf.js Improve test watching 2015-08-05 23:09:43 -07:00
package.json 0.9.0 2015-09-11 11:58:08 -07:00
wallaby.conf.js Add wallaby.js support, holy crap 2015-08-26 19:45:12 -07:00
webpack.config.js Add umd distribution builds via webpack 2015-08-04 10:13:34 -07:00

README.md

updeep

NPM version Build Status Code Climate

Dependency Status peerDependency Status devDependency Status

Easily update nested frozen objects and arrays in a declarative and immutable manner.

About

updeep makes updating deeply nested objects/arrays 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 (like lodash-fp).

Note that the parameters may be backwards from what you may be used to. updeep supports partial application, so the parameter order is: updeep(updates, object).

API and Examples

NOTE: Before updeep is 1.0, method names or semantics may change with a minor version bump.

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

var person = {
  name: {
    first: 'Jane',
    last: 'West'
  }
};

var result = u({ name: { first: 'Susan' } }, person);

expect(result).to.eql({ name: { first: 'Susan', last: 'West' } });

Multiple updates, including an array

var person = {
  name: {
    first: 'Mike',
    last: 'Smith'
  },
  scores: [12, 28]
};

var result = u({ name: { last: 'Jones' }, scores: { 1: 36 } }, person);

expect(result).to.eql({ name: { first: 'Mike', last: 'Jones' }, scores: [12, 36] });

Use a function

function increment(i) { return i + 1; }
var scoreboard = {
  scores: {
    team1: 0,
    team2: 0
  }
};

var result = u({ scores: { team2: increment } }, scoreboard);

expect(result).to.eql({ scores: { team1: 0, team2: 1 } });

When null or undefined object, updeep uses a default object

var result = u({ foo: 'bar' }, null);
expect(result).to.eql({ foo: 'bar' });

var result = u({ 0: 'foo', 1: 'bar' }, null);
expect(result).to.eql(['foo', 'bar']);

Partial application

function increment(i) { return i + 1; }

var addOneYear = u({ age: increment });
var result = addOneYear({ name: 'Shannon Barnes', age: 62 });

expect(result).to.eql({ name: 'Shannon Barnes', age: 63 });

ES6 computed properties

var key = 'age';

var result = u({ person: { [key]: 21 } }, { person: { name: 'Olivier P.', age: 20 } });

expect(result).to.eql({ person: { name: 'Olivier P.', age: 21 } });

u._

All updeep functions are curried. If you want to partially apply a function in an order other than the default argument order, you can use the placeholder.

function increment(i) { return i + 1; }
var updateJoe = u(u._, { name: "Joe Merrill", age: 21 });
var result = updateJoe({ age: increment });

expect(result).to.eql({ name: "Joe Merrill", age: 22 });

u.updateIn(path(, value)(, object))

Update a single value with a simple string or array path. Can be use to update nested objects, arrays, or a combination.

var result = u.updateIn('bunny.color', 'brown', { bunny: { color: 'black' } });

expect(result).to.eql({ bunny: { color: 'brown' } });
var result = u.updateIn('0.1.color', 'brown', [[{ color: 'blue' }, { color: 'red' }], []]);

expect(result).to.eql( [[{ color: 'blue' }, { color: 'brown' }], []]);
function increment(i) { return i + 1; }

var result = u.updateIn('bunny.age', increment, { bunny: { age: 2 } });

expect(result).to.eql({ bunny: { age: 3 } });
var result = u({ pets: u.updateIn([0, 'bunny', 'age'], 3) }, { pets: [{ bunny: { age: 2 } }] });

expect(result).to.eql({ pets: [{ bunny: { age: 3 } }] });

u.constant(object)

Sometimes, you want to replace an object outright rather than merging it. You'll need to use a function that returns the new object. u.constant creates that function for you.

var user = {
  name: 'Mitch',
  favorites: {
    band: 'Nirvana',
    movie: 'The Matrix'
  }
};

var newFavorites = {
  band: 'Coldplay'
};

var result = u({ favorites: u.constant(newFavorites) }, user);

expect(result).to.eql({ name: 'Mitch', favorites: { band: 'Coldplay' } });
var alwaysFour = u.constant(4);
expect(alwaysFour(32)).to.eql(4);

u.if(predicate(, updates)(, object))

Apply updates if predicate is truthy, or if predicate is a function. It evaluates to truthy when called with object.

function isEven(x) { return x % 2 === 0; }
function increment(x) { return x + 1; }

var result = u({ value: u.if(isEven, increment) }, { value: 2 });

expect(result).to.eql({ value: 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.

function isEven(x) { return x % 2 === 0; }
function increment(x) { return x + 1; }
function decrement(x) { return x - 1; }

var result = u({ value: u.ifElse(isEven, increment, decrement) }, { value: 3 });

expect(result).to.eql({ value: 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 increment(x) { return x + 1; }

var result = u({ values: u.map(increment) }, { values: [0, 1] });

expect(result).to.eql({ values: [1, 2] });
function increment(x) { return x + 1; }

var result = u.map(increment, [0, 1, 2]);

expect(result).to.eql([1, 2, 3]);
function increment(x) { return x + 1; }

var result = u.map(increment, { a: 0, b: 1, c: 2 });

expect(result).to.eql({ a: 1, b: 2, c: 3 });
var result = u.map({ a: 100 }, [{ a: 0 }, { a: 1 }]);

expect(result).to.eql([{ a: 100 }, { a: 100 }]);

u.omit(predicate(, object))

Remove properties. See _.omit.

var user = { user: { email: 'john@aol.com', username: 'john123', authToken: '1211..' } };

var result = u({ user: u.omit('authToken') }, user);

expect(result).to.eql({ user: { email: 'john@aol.com', username: 'john123' } });
var user = {
  user: {
    email: 'john@aol.com',
    username: 'john123',
    authToken: '1211..',
    SSN: 5551234
  }
};

var result = u({ user: u.omit(['authToken', 'SSN']) }, user);

expect(result).to.eql({ user: { email: 'john@aol.com', username: 'john123' } });

u.reject(predicate(, object))

Reject items from an array. See _.reject.

function isEven(i) { return i % 2 === 0; }

var result = u({ values: u.reject(isEven) }, { values: [1, 2, 3, 4] });

expect(result).to.eql({ values: [1, 3] });

u.withDefault(default(, updates)(, object))

Like u(), but start with the default value if the original value is undefined.

var result = u({ value: u.withDefault([], { 0: 3 }) }, {});

expect(result).to.eql({ value: [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 ===.

var result = u.is('friend.age', 22, { friend: { age: 22 } });

expect(result).to.eql(true);
function isEven(i) { return i % 2 === 0; }

var result = u.is('friend.age', isEven, { friend: { age: 22 } });

expect(result).to.eql(true);
var person = {
  person: {
    name: {
      first: 'Jen',
      last: 'Matthews'
    }
  }
};

// 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')
  )
});

expect(result).to.eql({ 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

  1. Fork it.
  2. Create your feature branch (git checkout -b my-new-feature).
  3. Run gulp to run tests and lint.
  4. Commit your changes (git commit -am 'Added some feature').
  5. Push to the branch (git push origin my-new-feature).
  6. Create new Pull Request.

Releasing New Version

  1. Login to npm, if you don't have access to the package, ask for it.

    $ npm login
    
  2. Make sure the build passes (best to let it pass on travis, but you can run it locally):

    $ gulp
    
  3. Bump the version:

    $ npm version major|minor|patch
    
  4. Update the CHANGELOG.md.

  5. Add the new version and corresponding notes.

  6. Add a link to the new version.

  7. Update the unreleased link compare to be based off of the new version.

  8. Publish and push:

    $ npm publish
    $ git push master --follow-tags
    

License

MIT ©2015 Substantial