Over the past few months, our team has started to strictly adhere to a core functional programming paradigm: data immutability. An immutable object is one whose state cannot be modified after it is created. Immutability is great if you are using libraries like React or Redux because it enables performance optimizations, such as the PureRenderMixin.

We loved the idea of immutability but faced some challenges when we refactored our app to be immutable, specifically, updating immutable objects. These challenges were particularly frustrating when managing collections and deeply nested objects because our update code was imperative and hard to maintain. We needed a declarative way to update objects while maintaining immutability, so we created updeep.

Introducing updeep

updeep makes updating deeply nested objects/arrays painless by allowing you to declare the updates you would like to make and updeep takes care of the rest. updeep recursively returns the same instance if no changes have been made, making it ideal when using reference equality checks to detect changes. updeep also comes with some utilities to enable a more declarative approach to updating objects.

For example, compare the imperative and declarative versions of code to update a deeply nested object:

import * as _ from 'lodash';

let user = {
  profile: {
    social: {
      twitter: {
        username: 'substantial',
        link: 'http://twitter.com'
      },
      instagram: {
        username: 'substance',
        link: 'http://instagram.com/substantial'
      }
    }
  }
};

// update twitter link and instagram username
user = _.deepClone(user);
user.profile.social.twitter.link = 'https://twitter.com/substantial';
user.profile.social.instagram.username = 'substantial';

// With updeep
import * as u from 'updeep';

user = u({
  profile: {
    social: {
      twitter: {
        link: 'https://twitter.com/substantial'
      },
      instagram: {
        username: 'substantial'
      }
    }
  }
}, user);

Or updating an object in a collection:

let accounts = {
  isLoaded: true,
  users: [
    { id: 1, isActive: true },
    { id: 2, isActive: false },
    { id: 3, isActive: true },
    { id: 4, isActive: true }
  ]
};

// Update user id 2 'isActive'
const users = accounts.users.map(user => {
  if (user.id === 2) {
    return { ...user, isActive: true };
  }

  return user;
});

accounts = { ...accounts, users };

// With updeep
import * as u from 'updeep';

accounts = u({
  users: u.map(u.if(u.is('id', 2), { isActive: true }))
}, accounts);

updeep exports two update functions, u and u.updateIn, and many update utilities such as u.if, u.map, u.withDefault, etc. Each can transform imperative code into declarative code. u.updateIn is great for updating single values in a nested object. For the example above, updating only a user’s twitter link becomes:

import * as u from 'updeep';

user = u.updateIn('profile.social.twitter.link', 'https://twitter.com/substantial', user);

As an added bonus, updeep freezes the result of any update, making it easy to enforce immutability without using another library such as Immutablejs. Freezing objects only occurs in development so that no extra overhead is present in production.

user = u.updateIn('profile.social.twitter.link', 'https://twitter.com/substantial', user);

user.profile.social.twitter.link = 'http://twitter.com/reactjs'
// TypeError: Cannot assign to read only property 'link' of [object Object]

Using updeep makes our code much more declarative and easier to understand. It works nicely with a functional programming style and integrates well with functional utility libraries such as Ramda or lodash-fp.

If you are interested in learning more about updeep, head over to GitHub to see the full docs and more examples.

Main image from Flickr user grantdaws.