Best way to deal with immutable data in JS
Tags: react, webdev, javascript, redux
Published: 2020-05-08 20:42:29

Hey, Devs😎 I don't know how I miss it before, but I find out the best way to deal with immutable data.

## Data and Structure types in JavaScript

1. Six Primitive types checked by <code>typeof</code> operator
  * <code>undefined</code> - <code>typeof undefined === 'undefined'</code>
  * <code>Boolean</code> - <code>typeof true === 'boolean'</code>
  * <code>String</code> - <code>typeof 'hello' === 'string'</code>
  * <code>Number</code> - <code>typeof 10 === 'number'</code>
  * <code>BigInt</code> - <code>typeof 10n === 'bigint'</code>
  * <code>Symbol</code> - <code>typeof Symbol() === 'symbol'</code>
2. <code>null</code> - special primitive type, <code>typeof null === 'object'</code>
3. <code>Object</code> Inclides <code>Array, Map, Set, WeekMap, WeekSet, Date</code>- <code>typeof {} === 'object'</code>
4. <code>Function</code> - <code>typeof () => {} === 'function'</code>


## Problem
JavaScript assignment works in two ways. For primary types (Boolean, String, Number, BigInt, null, Symbol) assignment returns the new value. For complex types (Object) it returns a reference (pointer in memory) and any changes will impact all entries because all these entries are just references on the same pointer in memory.

And the problem is that no guaranty that something will stay unchanged. The worst-case scenario is when the structure is used in different parts of the application. The mutation of this structure in one of the components can affect the bug in the whole application. And this bug is really hard to track. Where it was changed? What exactly was changed? Who also has access to the reference? But the history of change is not available and questions can’t be easily answered.

In React-Redux stack we are used to handling immutable data, but sometimes it can be very tedious with ES6 native way;

```
function updateVeryNestedField(state, action) {
  return {
    ...state,
    first: {
      ...state.first,
      second: {
        ...state.first.second,
        [action.someId]: {
          ...state.first.second[action.someId],
          fourth: action.someValue
        }
      }
    }
  }
}
```
Oh yeah😱 Looks familiar?

```
switch (action.type) {
 case ADD_NEW_AVAILABLE_COLOR_TO_CAR:{
  const { color, model, manufacturer } = action.payload
  return {...state, manufacturer: {
    ...state.manufacturer, [manufacturer]:
    {...state.manufacturers[manufacturers], models:
      {...state.manufacturers[manufacturers].models, [model]:
        {...state.manufacturers[manufacturers].models[model], options:
          {...state.manufacturers[manufacturers].models[model].options, colors:
          {...state.manufacturers[manufacturers].models[model].options.colors, [color]: true}
         }
       }
     }
   }
 }
 default: return state
}
```

Of course, you can say "hey buddy, you forgot about **immutable-js**"

{% github immutable-js/immutable-js %}

But I don't like it this way. It's an extra abstraction in your code with uncommon data structures for frontend developers. It seriously increases the entry threshold in your project for other developers. And debugging is really painful as hell. I have to click and click and click once again to expand the wrapped data in the console. However, it is just a simple nested list of objects. I can’t simply find out what’s inside😡

## Solution

{% github kolodny/immutability-helper %}

Library **immutable-helpers** represents a simple immutable helper **update**:

```
import update from ' immutable-helpers';

const newData = update(myData, {
  x: {y: {z: {$set: 7}}},
  a: {b: {$push: [9]}}
});
```

You can see it, right? It's really simple! The icing on the cake is a familiar approach that we really well know from [mongodb native driver](https://github.com/mongodb/node-mongodb-native):

```
db.products.update(
   { _id: 100 },
   { $set:
      {
        quantity: 500,
        details: { model: "14Q3", make: "xyz" },
        tags: [ "coats", "outerwear", "clothing" ]
      }
   }
)
```

List of available commands:
* {$push: array} push() all the items in array on the target.
* {$unshift: array} unshift() all the items in array on the target.
* {$splice: array of arrays} for each item in arrays call splice() on the * target with the parameters provided by the item.
* {$set: any} replace the target entirely.
* {$merge: object} merge the keys of object with the target.
* {$apply: function} passes in the current value to the function and updates it with the new returned value.

And finally my personal small example of how organically it fits into the Redux reducers:

```
const reducer = (state = initialState, action: IAppAction): TState => {
  switch (action.type) {
    case CONVERSATIONS_ADD: {
      const { conversation } = action.data;

      return update(state, {
        [conversation.counterpartId]: { $set: conversation },
      });
    }
    case CONVERSATION_READ_SUCCESS:
      return update(state, {
        [action.data.counterpartId]: { unread: { $set: 0 } },
      });
    default:
      return state;
  }
};
```

You are welcome! But don't forget, it's not the tools that make you a good developer.