Forever Functional: Immutable objects for safer state
Tags: functional, javascript, tutorial
Published: 2021-10-14 05:23:56

_by author [Federico Kereki](https://blog.openreplay.com/authors/federico-kereki)_

In our previous article, [Forever Functional: Injecting for Purity](https://blog.openreplay.com/forever-functional-injecting-for-purity) we mentioned that pure functions do not produce any observable side effects when they do their work, and this includes not modifying anything "in the outside". However, we all know that objects, even if declared as `const`, may be modified. Global state is thus a probable target for "side effects": any function may change it, and lead to hard-to-understand situations. In this article, we'll discuss how to work with immutable objects for safer state handling, so accidentally changing them will become harder or directly impossible.

It's impossible to enforce a rule that will make developers work in safe, guarded ways. However, if we can manage to make our data structures immutable (and we'll keep state in such an object) we'll control how the state is modified. We'll have to work with some interface that won't let us directly modify the original data, but rather produce a new object instead. (Observant readers may notice that this is exactly the idea behind [Redux](https://redux.js.org/): you don't directly modify state: you have to dispatch an action, which a reducer will process to produce a new state.)

Here we'll study the varied ways that JavaScript provides, such as object freezing and cloning, so we can get a basic understanding of what a full immutability solution needs. We won't try, however, to achieve 100% production-ready code, possibly dealing with [getters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get), [setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set), [private attributes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields), and more; for this I would rather suggest looking at available libraries such as [Immutable](https://immutable-js.com/), [Immer](https://immerjs.github.io/immer/), [Seamless-immutable](https://github.com/rtfeldman/seamless-immutable), and others.

We will consider the following options:
* Using const declarations to prevent (some) variables from being changed
* Freezing objects to avoid all modifications
* Creating a changed clone to avoid modifying the original object
* Avoiding mutator functions that modify the object to which they are applied
* Other more obscure ways

Let's get started!

## Using const declarations

The first solution we could think of is using const declarations to make variables immutable. However, this won't work with objects or arrays, because in JavaScript a `const` definition applies to the reference to the original object or array, and not to their contents. While we cannot assign a new object or array to a `const` variable, we can certainly modify its contents.

```javascript
const myself = { name: "Federico Kereki" };
myself = { name: "Somebody else" }; // this throws an error, but...
myself.name = "Somebody else";      // no problem here!
```

We cannot replace a `const` variable, but we can modify it. Using constants works for other types, such as booleans, numbers, or strings. However, when working with objects and arrays we'll have to resort to other methods, such as *freezing* or *cloning*, which we'll consider below.

## Avoiding mutator functions

A source of problems is that several JavaScript methods work by mutating the object to which they are applied. If we just use any of these methods, we will be (knowingly or unknowingly) causing a side effect. Most of the problems are related to [arrays and methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#array_methods) such as:

- `fill()` to fill an array with a value
- `sort()` to sort an array in place
- `reverse()` to reverse the elements of an array, in place
- `push()`, `pop()`, `shift()`, `unshift()`, and `splice()` to add or remove elements from an array

This behavior is different from other methods, like `concat()`, `map()`, `filter()`, `flat()`, that do not modify the original array or arrays. There's an easy way out for this, fortunately. Whenever you want to use some mutator method, generate a copy of the original array, and apply the method to it. Let me give you an example out of my [Mastering JavaScript Functional Programming](https://www.packtpub.com/product/mastering-javascript-functional-programming-second-edition/9781839213069) book. Suppose we want to get the maximum element of an array. A possible way we could think of is first sorting the array and then popping its last element.

```javascript
const maxStrings = a => a.sort().pop();
let countries = ["Argentina", "Uruguay", "Brasil", "Paraguay"];
console.log(maxStrings(countries)); // "Uruguay"
console.log(countries); // ["Argentina", "Brasil", "Paraguay"]
```

Oops! We got the maximum, but the original array got trashed. We can rewrite our function to work with copies.

```javascript
const maxStrings2 = a => [...a].sort().pop();
const maxStrings3 = a => a.slice().sort().pop();
let countries = ["Argentina", "Uruguay", "Brasil", "Paraguay"];
console.log(maxStrings2(countries)); // "Uruguay"
console.log(maxStrings3(countries)); // "Uruguay"
console.log(countries); // ["Argentina", "Uruguay", "Brasil", "Paraguay"] - unchanged
```

Our new versions are functional, without any side effects, because now we are applying mutator methods to a copy of the original array. This is a solution, but it depends on developers being careful; let's see if we can do better.

## Freezing objects

JavaScript provides a way to avoid accidental (or intentional!) modifications to arrays and objects: freezing. A frozen array or object cannot be modified, and any attempt at doing this will silently fail without even an exception. Let's review the example from the previous section.

```javascript
const myself = { name: "Federico Kereki" };
Object.freeze(myself);
myself.name = "Somebody else";      // won't have effect
console.log(myself.name);           // Federico Kereki
```

This also works with arrays.

```javascript
const someData = [ 22, 9, 60 ];
Object.freeze(someData);
someData[1] = 80;                   // no effect
console.log(someData);              // still [ 22, 9, 60 ]
```

Freezing works well, but it has a problem. If a frozen object contains unfrozen objects, those *will* be modifiable.

```javascript
const myself = { name: "Federico Kereki", someData: [ 22, 9, 60 ] };
Object.freeze(myself);
myself.name = "Somebody else";      // won't have effect
myself.someData[1] = 80;            // but this will!
console.log(someData);              // [ 22, 80, 60 ]
```

To really achieve immutable objects through and through, we have to write code that will recursively freeze the objects and all their attributes, and their attributes' attributes, etc. We can make do with a deepFreeze() function as the following.

```javascript
const deepFreeze = obj => {
  if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
    Object.freeze(obj);
    Object.getOwnPropertyNames(obj).forEach(prop => deepFreeze(obj[prop]));
  }
return};
```

Our `deepFreeze()` function freezes the object in place, the same way `Object.freeze()` does, keeping the semantics of the original method. When coding this one must be careful with possibly circular references. To avoid problems, we first freeze an object, and only then do we deep freeze its properties. If we apply the recursive method to an already frozen object, we just don't do anything.

## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).


## Creating (changed) clones

Freezing is a possibility, but sometimes one needs to produce a new (mutated) object or array. As we mentioned, [Redux](https://redux.js.org/) works this way: reducers are functions that get the current state and an action (some new data) and apply the new data to the state to produce a new, updated state. Redux prohibits modifying the current state: a new object must be produced instead. 

How can we clone an object? Just copying won't do: something like the following just creates a new reference to the same object.

```javascript
const myself = { name: "Federico Kereki", otherData: { day:22, month:9 } };
const newSelf = myself;    // not a clone; just a reference to myself
```

For simple objects, spreading is a (partial!) solution.

```javascript
const newSelf = { ...myself };
```

However, `newSelf.otherData` still points to the original object in the `myself` object.

```javascript
newSelf.otherData.day = 80;
console.log(myself.otherData.day);   // 80, not 22!
```

We can write a `deepCopy()` function that, whenever copying an object, builds a new one by invoking the appropriate constructor. The following code is also taken from my book, referred above.

```javascript
const deepCopy = obj => {
  let aux = obj;
  if (obj && typeof obj === "object") {
    aux = new obj.constructor();
    Object.getOwnPropertyNames(obj).forEach(
      prop => (aux[prop] = deepCopy(obj[prop]))
    );
  }
  return aux;
};
```

So, we have two ways to avoid problems with objects: freezing (to avoid modifications) and cloning (to be able to produce a totally separate copy). We can achieve safety, now!

## Other (more obscure) ways

These are not the only possible solutions, but they are enough to get you started. We could also consider some other methods, such as:

* adding setters to all attributes, so modifications are forbidden

* writing some generic `setAttributeByPath()` function that will take an object, a path (such as `"otherData.day"`) and a new value, and produce an updated object by making a clone of the original one

* or getting into even more advanced Functional Programming ideas such as *optics*. This includes  *lenses* (to get or set values from objects) and *prisms* (like lenses, but allowing for missing attributes)... But that's better left to another article!

Finally, don't ignore the possibility of using some available libraries, such as [immutable.js](https://immutable-js.com/) and many others.

## Summary

In this article, we've gone over the possible problems caused by unwanted side effects when dealing with objects and arrays. We've considered some ways to solve them, including freezing, cloning, and the avoidance of mutators. All these tools are very convenient, and many Web UI frameworks assume or require immutability, so now you're ahead of those requirements!