SkillAgentSearch skills...

Updeep

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

Install / Use

/learn @substantial/Updeep
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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][npm-image]][npm-url] [![Build Status][circleci-image]][circleci-url] [![Code Climate][codeclimate-image]][codeclimate-url]

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][currying] [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][currying], 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: 'bill@example.com',
  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: '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

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 =

Related Skills

View on GitHub
GitHub Stars1.2k
CategoryDevelopment
Updated1mo ago
Forks38

Languages

JavaScript

Security Score

95/100

Audited on Feb 1, 2026

No findings