Understanding updateIn & keyPath in ImmutableJS

August 10, 2016

ImmutableJS is a neat library, especially when used in combination with Redux. I’m not ashamed to admit, though, that I found the docs confusing at first. For example, look at the method signature for Map#updateIn():


updateIn() is a useful function to use when you’re interacting with Redux. I had to understand it. In the end, what helped me become productive with updateIn() was understanding the keyPath parameter.

The key (sorry) to understanding keyPath is to realize it’s just an array of values that Immutable will call get with as it works it’s way through whatever data structure you’re calling updateIn() on. It doesn’t matter if the value is a Map key or a List index. This works because both Map#get and List#get inherit from Iterable#get (https://facebook.github.io/immutable-js/docs/#/Iterable/get).

Consider the following data structure, which is a Map of endpoints, keyed on their id. The values are a List of Maps that represent that endpoint’s “subscriptions” to a given event (enabled: true|false):

const endpoints = fromJS({
  "abc-123": [
    {
      id: 111,
      endpoint_id: "abc-123",
      event_key: "signup_success",
      enabled: true,
    },
    {
      id: 222,
      endpoint_id: "abc-123",
      event_key: "signup_failure",
      enabled: false,
    },
  ],
  "def-456": [
    {
      id: 333,
      endpoint_id: "def-456",
      event_key: "signup_success",
      enabled: false,
    },
    {
      id: 444,
      endpoint_id: "def-456",
      event_key: "signup_failure",
      enabled: true,
    },
  ],
});


To get all the subscriptions for a given endpoint, we use Map#get as follows:

subscriptions = endpoints.get("abc-123");
keyPath = ["abc-123"]

Since subscriptions is now a List, we can get the first element using List#get, passing it the index of the element we want to access:

subscription = subscriptions.get(0);
keyPath = [0]

We could do this in one shot:

subscription = endpoints.get("abc-123").get(0);
keyPath = ["abc-123", 0]

What’s neat is that we can construct a keyPath down to an attribute of an element. Say we wanted to get the enabled attribute of the above endpoint:

enabled = endpoints.get("abc-123").get(0).get('enabled');
keyPath = ["abc-123", 0, "enabled"]

Now that we know how to construct a keyPath to any point in a deeply nested data structure, we can move to understanding the updater parameter of updateIn.

The simplest way to understand updater is that it’s a function that will be passed as it’s parameter the result of calling get on the final element in the keyPath array. It should return the new value for that…value. It’s easier to show in code.

Let’s say I wanted to flip the enabled flag for the subscription represented by element 0 for endpoint abc-123:

endpoints.updateIn(["abc-123", 0, 'enabled'], isEnabled => !isEnabled);

In the above example, I constructed the keyPath right down to the enabled flag (the first parameter), and the second parameter is a function that will be passed the current value of enabled as a parameter called isEnabled, and will simply return opposite boolean value of isEnabled.

You could back the keyPath up one level, as ["abc-123", 0], which would give you the entire subscription Map at that index, and then return a new Map with the enabled flag flipped. But in my (limited) experience, I’ve not updated more than one attribute of a given object at a time in a reducer. Hope this helps!

You can discuss this post at Hacker News.