• Stars
    star
    1,187
  • Rank 39,407 (Top 0.8 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 9 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

updeep

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

This library is still maintained but is considered feature complete. Please feel free to use it and report any issues you have with it.

Join the chat at https://gitter.im/substantial/updeep NPM version Build Status Code Climate

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. Not only that, but updeep assumes that every object passed in to update is immutable, so it may freeze objects passed in as well. Note that the freezing only happens in development.

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

Full example

const u = require('updeep');

const person = {
  name: { first: 'Bill', last: 'Sagat' },
  children: [
    { name: 'Mary-Kate', age: 7 },
    { name: 'Ashley', age: 7 }
  ],
  todo: [
    'Be funny',
    'Manage household'
  ],
  email: '[email protected]',
  version: 1
};

const inc = function(i) { return i + 1; }
const eq = function(x) { return function(y) { return x == y } };

const newPerson = u({
  // Change first name
  name: { first: 'Bob' },
  // Increment all children's ages
  children: u.map({ age: inc }),
  // Update email
  email: '[email protected]',
  // 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: '[email protected]',
//  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

Object properties:

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

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

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

Array elements:

const scoreboard = {
  scores: [12, 28]
};

const result = u({ scores: { 1: 36 } }, scoreboard);

expect(result).to.eql({ scores: [12, 36] });

Multiple updates

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

const 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; }
const scoreboard = {
  scores: {
    team1: 0,
    team2: 0
  }
};

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

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

Array Manipulation

Non-trivial array manipulations, such as element removal/insertion/sorting, can be implemented with functions. Because there are so many possible manipulations, we don't provide any helpers and leave this up to you. Simply ensure your function is pure and does not mutate its arguments.

function addTodo(todos) { return [].concat(todos, [{ done: false }]); }

const state = {
  todos: [
    { done: false },
    { done: false }
  ]
};

const result = u({ todos: addTodo }, state);

expect(result).to.eql({ todos: [{ done: false }, { done: false }, { done: false }]});

lodash/fp is one of the many libraries providing good utility functions for such manipulations.

import fp from 'lodash/fp';

let state = {
  todos: [
    { done: true },
    { done: false }
  ]
};

// add a new todo
state = u({ todos: fp.concat({ done: false }) }, state);
expect(state).to.eql({ todos: [{ done: true }, { done: false }, { done: false }]});

// remove all done todos
state = u({ todos: fp.reject({ done: true }) }, state);
expect(state).to.eql({ todos: [{ done: false }, { done: false }]});

When null or undefined object, updeep uses a default object

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

Partial application

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

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

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

ES6 computed properties

const key = 'age';

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

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

u.freeze

Freeze your initial state to protect against mutations. Only performs the freezing in development, and returns the original object unchanged in production.

const state = u.freeze({ someKey: "Some Value" })
state.someKey = "Mutate" // ERROR in development

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; }
const updateJoe = u(u._, { name: "Joe Merrill", age: 21 });
const 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. Can also be used to update every element of a nested array with '*'.

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

expect(result).to.eql({ bunny: { color: 'brown' } });
const 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; }

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

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

expect(result).to.eql({ pets: [{ bunny: { age: 3 } }] });
const result = u.updateIn('todos.*.done', true, {
  todos: [
    { done: false },
    { done: false },
  ]
});

expect(result).to.eql({
  todos: [
    { done: true },
    { done: true },
  ]
});

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.

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

const newFavorites = {
  band: 'Coldplay'
};

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

expect(result).to.eql({ name: 'Mitch', favorites: { band: 'Coldplay' } });
const 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; }

const 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; }

const 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; }

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

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

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

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

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

expect(result).to.eql({ a: 1, b: 2, c: 3 });
const 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.

const user = { user: { email: '[email protected]', username: 'john123', authToken: '1211..' } };

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

expect(result).to.eql({ user: { email: '[email protected]', username: 'john123' } });
const user = {
  user: {
    email: '[email protected]',
    username: 'john123',
    authToken: '1211..',
    SSN: 5551234
  }
};

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

expect(result).to.eql({ user: { email: '[email protected]', username: 'john123' } });

u.omitted

A property updated to this constant will be removed from the final object. Useful when one wishes to remove and update properties in a single operation.

const user = { email: '[email protected]', username: 'john123', authToken: '1211..' };

const result = u({ authToken: u.omitted, active: true }, user);

expect(result).to.eql({ user: { email: '[email protected]', username: 'john123', active: true } });

u.omitBy(predicate(, object))

Remove properties. See _.omitBy.

const user = {
  user: {
    email: '[email protected]',
    username: 'john123',
    authToken: '1211..',
    SSN: 5551234
  }
};

function isSensitive(value, key) { return key == 'SSN' }
const result = u({ user: u.omitBy(isSensitive) }, user);

expect(result).to.eql({ user: { email: '[email protected]', username: 'john123', authToken: '1211..' } });

u.reject(predicate(, object))

Reject items from an array. See _.reject.

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

const 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.

const 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 ===.

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

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

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

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

// Update person's last name to Simpson if their first name is Jen
const result = u({
  person: u.if(
    u.is('name.first', 'Jen'),
    u.updateIn('name.last', 'Simpson')
  )
}, person);

expect(result).to.eql({ person: { name: { first: 'Jen', last: 'Simpson' } } });

Install

$ npm install --save updeep

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 yarn test and yarn lint to run tests and lint.
  4. Commit your changes (git commit -am 'Some change is made).
  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 circleci, but you can run it locally):

    $ npm run build
  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 origin master --follow-tags

See Also

A Dash version of the documention is also available as part of their user-contributed docsets. For Zeal users, the user-contributed docsets can be accessed via zealusercontributions.herokuapp.com.

License

MIT Β©2015 Substantial

More Repositories

1

choosy

An elegant way to give your users the freedom to choose favorite apps
Objective-C
293
star
2

sinon-stub-promise

Synchronous Promise stubbing for Sinon.JS
JavaScript
86
star
3

choosy-data

Effortlessly enable your users to choose default apps for external actions without writing any custom code.
24
star
4

react-media-queryable

Trigger state updates when a media query matches the current state of the browser
JavaScript
12
star
5

atomfiles

Substantial's Atom config
CoffeeScript
11
star
6

browser_stack_button

Ruby
7
star
7

knife_sous

Knife plugin that uses a DSL to manage knife-solo nodes.
Ruby
7
star
8

kiosk-manager-chrome

JavaScript
7
star
9

capistrano-fast_deploy

Ruby
5
star
10

sous-chef

Manage knife-solo nodes
Ruby
5
star
11

remap

Elixir
4
star
12

mapify

Make your levels in Unity3d out of ASCII text files with this plugin
C#
4
star
13

cap-strap

Ruby
3
star
14

substantial-dash-server

Ruby
3
star
15

restore

Restore from backups generated by the backup gem
Ruby
3
star
16

node-transloadit-development_notify_url_proxy

A proxy for working with transloadits notify_url while on a development machine (which can't be notified on the public internet).
JavaScript
3
star
17

wolfman

Ruby
2
star
18

saucier-example

Ruby
2
star
19

guard-simple_shell

Ruby
2
star
20

ember-cli-styleguide

Generate an HTML styleguide from CSS containing Markdown documentation & examples.
JavaScript
2
star
21

gatsbygram

JavaScript
1
star
22

xcoder-testflight

Provide a task in Xcoder to push a build to TestFlight
Ruby
1
star
23

substantial-dash-ios

Objective-C
1
star
24

saucier

Ruby
1
star
25

cookbook-xvfb

Ruby
1
star
26

cookbook-phantomjs

Ruby
1
star
27

circleci-orbs

1
star
28

cookbook-solr

Ruby
1
star
29

mapify-example

Unity3d example for the mapify project
C#
1
star
30

expect-to-be-a-promise

A chai matcher to assert the return value of a function is a thenable.
JavaScript
1
star
31

cookbook-qt

Ruby
1
star
32

refinerycms-carousel-demo

Ruby
1
star
33

rspec-spy

Enables AAA testing for rspec-mock
Ruby
1
star
34

SubCommander

Objective-C REST Client for AFNetworking
Objective-C
1
star
35

pwa-emoji-search

JavaScript
1
star
36

cookbook-firefox

Ruby
1
star
37

wemux-pair

Firewall-punching remote pairing with wemux made easy
Ruby
1
star
38

canvas-test

Objective-C
1
star