Unlike most in the world of JavaScript, data immutability is bound to stick with us for a while, and for good reason: firstly, because it’s not it’s a way of coding (and thinking in code) that promotes clarity, ease of use and understanding data flow, and makes code less prone to errors. trends a trend: But while newer flavours of the JS language gives us a more robust to work with than ever before, without the use of libraries like , things can still look a little bit scary when you put it all together. Getting comfortable with reading and writing the most common use cases is very helpful. toolset Immutable.js In this short post, we’ll look at pure JS ways (with ES2015++, and yes, I may have just invented this notation) to, add, remove, and update deeply nested properties in Objects, Arrays, and finding common patterns to reproduce these operations. Playground: Direct link to the JS Bin Objects Let’s take an initial object, , which we can think of as being a piece of application state, that we want to mutate. À là redux thinking, we should always return a new copy of this state and . person never change it directly const person = {name: 'Ricardo',location: 'Berlin',interests: { coffee: 9, climbing: 9, wasps: 0 }}; Changing a simple object property Modifying a top level property is remarkably simple using . We'll explore more of its use cases (and alternatives) in a little bit, but for now let's simply create a modified copy of our object with set to "Douglas". Object.assign name const updatedPerson = Object.assign({}, person, {name: 'Douglas'}); . We’re telling Object.assign to . The rest of our object looks the same. Simples take this empty {}, apply person on top, and modify the name property Changing deeply nested properties Here’s a common mistake when using to copy an object: forgetting to copy the we're trying to mutate. Let's say we want to change the interest to and from "Berlin" to the "Moon" (a common train route from Berlin). What if we try the following application: Object.assign inner objects coffee 10 location const updated = Object.assign({}, person, {location: 'Moon',interests: {coffee: 10 // }}); Crap! Only this one is copied On the surface, it might seem like this works, but this doesn’t copy the rest of the object. It will leave us with an updated and , but it won't copy or . No one needs wasps, anyway. But how do we solve this? interests {coffee: 10} location: 'Moon' climbing wasps Instead, we need to also deeply copy the object, like so: interests const updated = Object.assign({}, person, {location: 'Moon',interests: person.interests, {coffee: 10 // All other interests are copied})}); Object.assign({}, Notice the double . A bit verbose, in truth, as all objects need to be assigned in order not to lose properties. Object.assign Spread operators We can make this look more tidy by making use of the operator, which takes the form of — in fact, the previous example can be re-written as: spread ... const updated = {...person,interests: { coffee: 10,}} ...person.interests, Much nicer to look at! Spread operators are so incredible that you should definitely . read more about them at MDN Deleting properties Now, onto deleting (or removing) properties from objects. The keyword is a mutating action, so we can't use it when we're thinking about immutable data. delete There’s a few different ways to go around it, some more efficient than others. One (slow-ish) approach is to recreate our entire object, but . Let’s create a function that accepts our object, and the name of the property we would like to see removed: ignoring the properties we want to be removed const removeProperty = (obj, property) => {return Object.keys(obj).reduce((acc, key) => {if ( ) {return { }return acc;}, {})} key !== property ...acc, [key]: obj[key]} Note: this was written in long form for readability’s sake. You can omit some of those return statements. It looks a bit convoluted, but what’s happening is pretty simple: for each key that the one we passed, we keep adding them to the accumulator, returned by the function. So now, if we wanted the property removed from our object, we can use this like so: is not reduce interests person const updated = removeProperty(person, 'interests'); Which would give us a brand new copy of the object, except for that one ignored property: { name: 'Ricardo', location: 'Berlin', } Aside: using lodash If you’re using in your project, then you can make use of some its methods to help you change objects. , which more often than not will mess up your immutable data. An exception, however, is , which you can use to delete a property from an object. lodash However, you should note that by default, some of lodash’s methods mutate the original objects the [_.omit](https://lodash.com/docs/4.17.4#omit) method Once again, let’s try and remove the property like we did before, but using lodash. This time, we'll write it in a reducer-style function, just as an example: interests import { omit } from lodash; const reducer = (state, action) => {switch (action.type) {case 'DELETE_KEY':return omit(state, action.key);default:return state;}} This will work, even without the subset of lodash. So if you’re already using lodash, you’ll get this for free. We could use it like this: /fp const newState = reducer(person, {type: 'DELETE_KEY',key: 'interests'}); …which would give us the same result. Once again, be weary of using some lodash methods when reassigning data, as most of their methods mutate the original object . Consider using the /fp subset variation. More complex updating It can be hard to grasp how to mix and match these operations for objects. Recombining properties, while updating them at the same time, is a pattern very commonly used with Redux reducers. For practice, let’s look at an example of a more complex operation. Consider our original data, an array of users with a name and an ID: const users = [{name: 'john', id: 176},{name: 'gary', id: 288},{name: 'louise', id: 213}]; In , it’s common practice to , by having data grouped by ID for easier lookups. So let’s say this is what we want to do: we want a which has the users . For kicks, let’s also have the Redux normalise your application state new array grouped by ID first letter of their names uppercased. In short, we want to go from the first table, to the second one: How do we return the as a key, though? This is where you'll see the notation. It allows you to dynamically pull in the value and use it as a key. So with that in mind, let's write our function that also uppercases the first letter: object.id [item.id]: something byId const byId = (state) => state.reduce((acc, item) => ({...acc, : Object.assign({}, item, {name: })}), {}) [item.id] item.name.charAt(0).toUpperCase() + item.name.slice(1) If this method could talk, here’s what it would say: Hey, you there: For my state, apply the _reduce_ method, which will give you an accumulator starting with an empty {}, and all my items. For each one, spread the accumulated properties, but add a new key with the value of each [item.id]. Inside each one of those, make a copy of _item_ , but also modify its _name_ property while you’re at it. This will , spreading all their values into each object, and modify their properties to have the first character uppercase. return a new object with the ID of each user as the key name What if we wanted to update more properties, other than just the name of the user? This is where you’ll think about combining in order to manipulate the data as you need, but always returning a new copy. Let’s refactor this a little bit by creating a function: pure functions updateUser const = (user) => Object.assign({}, user, {name: user.name.charAt(0).toUpperCase() + user.name.slice(1)}); updateUser const byId = (state) => state.reduce((acc, item) => ({...acc,[item.id]: (item),}), {}) updateUser All we need now to get a new piece of state with our users grouped by ID is simply: const usersById = byId(users); Arrays Cool, so what about immutability in arrays? Let’s consider an original piece of immutable data: const original = ['a', 'c', 'd', 'e']; Having an array, you would often want to do one of the following: Insert an item by index Remove an item by index Remove by item Insert an item to the end Inserting by index We’ve conveniently forgot to add next to the value in our index. Oh no, what a tremendous disaster for our alphabet app! How do we insert an item at a given index, in an immutable fashion? One way to think about it is to: b a Copy the array until the specified index Insert our item Copy the rest of the array from the specified index So we could write a helper function with the following signature: insertByIndex = (state, newItem, insertAt) Where _state_ is the original array, _newItem_ is the value of the item we'd like to add, and _insertAt_ is a number (index) at which location we want to insert our _newItem_ . A simple way to write such a helper function could be the following: const insertByIndex = (state, , insertAt) => [...state.slice(0, insertAt), ,...state.slice(insertAt)] newItem newItem Wait, what? Okay, let’s break this down. We’ve already seen that the operator (...) copies values, and that's exactly what we're doing here. First, we're returning a new Array; copy it from the beginning until our index, insert our new value ( ), then copy the rest of the array from there. spread b So an example of its usage would be: insertByIndex(original, 'b', 1)// ["a", "b", "c", "d", "e"] Removing by index Removing an Array by index is much simpler, luckily, as long as we can afford to use . Let's think: The method gives us the index value as the second argument, so that means we want to return . Array.filter filter all values which don't have the index of N const removeByIndex = (arr, at) => arr.filter((item, idx) => ); idx !== at Removing by item If you want to remove an item directly (say, , instead of its index), we can still use like we did previously, but we'll filter out the item itself and forget about the index: b filter const removeByItem = (arr, value) => arr.filter((item) => ); item !== value Adding an item Adding an item to the end of an Array is also quite simple, but In fact, good old is your immutable friend. Using mutates the original array, which will inevitably lead to unpredictable behaviour. don’t you dare to think of push! concat() .push const addItem = (arr, value) => arr. (value); concat So if we wanted to add to our alphabet array (why wouldn't you?), we could do: banana addItem(original, 'banana') // ["a", "c", "d", "e", "banana"] Food for thought Even if you’re using an immutable library like Immutable.js or the , it’s still a great idea to have a good grasp of how immutability works with simple . Immutable.js comes with a hefty weight as a dependency, and there are alternatives such as the also popular . fp flavour of lodash JavaScript dot-prop-immutable When should you use a library for immutability or go raw with JavaScript? That really depends on the complexity of your data changes, the amount of overhead you can bring both to your codebase and to your team (it’s ). I’d argue that knowing the barebones implementation of most patterns is useful to understand, especially when using Redux or any other similar pattern that thrives in code immutability. yet another thing to learn Very useful resources: Intro to Immutable.js Redux Immutable Patterns More ES2015 examples Spread Syntax: MDN Anything I might have overlooked or gotten wrong? Don’t be afraid to ping me on . Twitter Originally published on my personal blog at blog.ricardofilipe.com