DEV Community

Cover image for Best way to deal with immutable data in JS
Slava Borodulin
Slava Borodulin

Posted on

Best way to deal with immutable data in JS

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 typeof operator
    • undefined - typeof undefined === 'undefined'
    • Boolean - typeof true === 'boolean'
    • String - typeof 'hello' === 'string'
    • Number - typeof 10 === 'number'
    • BigInt - typeof 10n === 'bigint'
    • Symbol - typeof Symbol() === 'symbol'
  2. null - special primitive type, typeof null === 'object'
  3. Object Inclides Array, Map, Set, WeekMap, WeekSet, Date- typeof {} === 'object'
  4. Function - typeof () => {} === 'function'

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
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

GitHub logo immutable-js / immutable-js

Immutable persistent data collections for Javascript which increase efficiency and simplicity.

Immutable collections for JavaScript

Build Status Chat on slack

Read the docs and eat your vegetables.

Docs are automatically generated from README.md and immutable.d.ts Please contribute! Also, don't miss the wiki which contains articles on additional specific topics. Can't find something? Open an issue.

Table of contents:

Introduction

Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data.

Immutable.js provides many Persistent Immutable data structures including List, Stack, Map, OrderedMap, Set, OrderedSet and Record.

These data structures are highly efficient on modern…

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 logo kolodny / immutability-helper

mutate a copy of data without changing the original source

immutability-helper

NPM version Build status Test coverage Downloads Minified size Gzip size

Mutate a copy of data without changing the original source

Setup via NPM

npm install immutability-helper --save
Enter fullscreen mode Exit fullscreen mode

This is a drop-in replacement for react-addons-update:

// import update from 'react-addons-update';
import update from 'immutability-helper';

const state1 = ['x'];
const state2 = update(state1, {$push: ['y']}); // ['x', 'y']
Enter fullscreen mode Exit fullscreen mode

Note that this module has nothing to do with React. However, since this module is most commonly used with React, the docs will focus on how it can be used with React.

Overview

React lets you use whatever style of data management you want, including mutation. However, if you can use immutable data in performance-critical parts of your application it's easy to implement a fast shouldComponentUpdate() method to significantly speed up your app.

Dealing with immutable data in JavaScript is more difficult than in languages designed for it…

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]}}
});
Enter fullscreen mode Exit fullscreen mode

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:

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

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;
  }
};
Enter fullscreen mode Exit fullscreen mode

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

Top comments (2)

Collapse
 
josemunoz profile image
José Muñoz

Since you mentioned Redux, there's an official abstraction that handles immutability for you in a very ergonomic way, it uses immer.js internally which is also another great immutability library.

GitHub logo reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development

Redux Toolkit

build status npm version npm downloads

The official, opinionated, batteries-included toolset for efficient Redux development

(Formerly known as "Redux Starter Kit")

Installation

Using Create React App

The recommended way to start new apps with React and Redux Toolkit is by using the official Redux+JS template for Create React App, which takes advantage of React Redux's integration with React components.

npx create-react-app my-app --template redux
Enter fullscreen mode Exit fullscreen mode

An Existing App

Redux Toolkit is available as a package on NPM for use with a module bundler or in a Node application:

# NPM
npm install @reduxjs/toolkit
# Yarn
yarn add @reduxjs/toolkit
Enter fullscreen mode Exit fullscreen mode

It is also available as a precompiled UMD package that defines a window.RTK global variable The UMD package can be used as a <script> tag directly.

Purpose

The Redux Toolkit package is intended to be the standard way to write Redux logic. It was originally created to help address three common concerns about Redux:

  • "Configuring a…

GitHub logo immerjs / immer

Create the next immutable state by mutating the current one

Immer

npm Build Status Coverage Status code style: prettier OpenCollective OpenCollective Gitpod Ready-to-Code

Create the next immutable state tree by simply modifying the current tree

Winner of the "Breakthrough of the year" React open source award and "Most impactful contribution" JavaScript open source award in 2019

Contribute using one-click online setup

You can use Gitpod (a free online VS Code like IDE) for contributing online. With a single click it will launch a workspace and automatically:

  • clone the immer repo.
  • install the dependencies.
  • run yarn run start.

so that you can start coding straight away.

Open in Gitpod

Documentation

The documentation of this package is hosted at immerjs.github.io/immer/

Support

Did Immer make a difference to your project? Join the open collective at opencollective.com/immer!

Release notes

github.com/immerjs/immer/releases




Collapse
 
vborodulin profile image
Slava Borodulin • Edited

Yes, immer is a great library and with redux you can do amazing things but in general, immer try to solve too many problems(patches, async usage). So immutable-helpers for me more intuitive and simple approach with single responsibility and zero threshold