Fast and lightweight library helps you to update js objects without mutating them

Overview

immhelper

Fast and lightweight library helps you to update js objects without mutating them

Install with npm

npm install immhelper --save

Features

  1. Extreme fast
  2. Lightweight
  3. Provide many powerful mutating actions
  4. Easy to define custom mutating actions
  5. Support batch processing per spec
  6. Support pipe processing per spec
  7. Support deep updating with target path
  8. Support sub spec with filter
  9. Support named mutating actions
  10. Support typescripts auto complete
  11. Support proxy for selecting and updating target
  12. Support low level API to update spec for special cases
  13. Support conditional update actions: if, unless, switch

Playground

https://p5243pkx6q.codesandbox.io/

Change Logs:

1.0.35: Improve performance. Become fastest package for copying immutable objects.

Benchmarks (Fastest to Slowest)

Show details

Normal

immhelper: Total elapsed = 72 ms (read) + 2344 ms (write) = 2416 ms
Object.assign: Total elapsed = 104 ms (read) + 2394 ms (write) = 2498 ms
immutable-assign: Total elapsed = 82 ms (read) + 3814 ms (write) = 3896 ms
immer: Total elapsed = 74 ms (read) + 7490 ms (write) = 7564 ms
seamless-immutable: Total elapsed = 103 ms (read) + 60833 ms (write) = 60936 ms
immutability-helper: Total elapsed = 84 ms (read) + 65249 ms (write) = 65333 ms
update-immutable: Total elapsed = 88 ms (read) + 71726 ms (write) = 71814 ms

With Deep Freeze

Object.assign: Total elapsed = 107 ms (read) + 30407 ms (write) = 30514 ms
immhelper: Total elapsed = 96 ms (read) + 33167 ms (write) = 33263 ms
immer: Total elapsed = 103 ms (read) + 39337 ms (write) = 39440 ms
immutable-assign: Total elapsed = 102 ms (read) + 46764 ms (write) = 46866 ms
immutability-helper: Total elapsed = 104 ms (read) + 105779 ms (write) = 105883 ms
update-immutable: Total elapsed = 108 ms (read) + 107985 ms (write) = 108093 ms

Summary

1.03x Faster than Object.assign
1.61x Faster than immutable-assign
3.13x Faster than immer
25.22x Faster than seamless-immutable
27.04x Faster than immutability-helper
29.72x Faster than update-immutable

Samples

import {
  update,
  $push,
  $unshift,
  $splice,
  $assign,
  $toggle,
  $unset,
  $set,
  $remove
} from "immhelper";

const original = {
  a: {
    b: {
      c: {
        d: {
          e: {
            f: {}
          }
        }
      }
    }
  },
  arrayPush: [],
  objMerge: {
    name: "Peter"
  },
  toggleMe: false,
  toggleMyProp: {
    done: false,
    completed: true
  },
  removeSecond: [1, 2, 3, 4],
  removeAppleAndBanana: ["Orange", "Apple", "Banana"],
  unsetMyProp: {
    data1: new Date(),
    data2: true
  },
  sqrt: 100,
  doubleItems: [1, 2, 3, 4, 5, 6, 7, 8],
  swapItems: ["left", "right"],
  increaseProps: {
    one: 1,
    two: 2,
    three: 3
  },
  removeByIndexes: [1, 2, 3, 4],
  batchProcessing: {},
  pipeProcessing: "hello",
  doubleOddNumbers: [1, 2, 3, 4],
  parentNode: {
    childNode: {}
  },
  parentNodes: [{ id: 0 }, { id: 1 }],
  updateTree: {
    text: "root",
    children: [
      {
        text: "child 1",
        data: {},
        children: [{ text: "child 1.1" }]
      },
      {
        text: "child 2",
        data: {},
        children: [{ text: "child 2.1" }, { text: "child 2.2" }]
      }
    ]
  },
  usingIfToUpdate: {
    value: 1
  },
  usingUnlessToUpdate: {
    dataLoaded: false
  },
  usingSwitchToUpdate1: 1,
  usingSwitchToUpdate2: {
    value: true
  },
  usingFilter: [1, 2, 3, 4, 5],
  unsetWithFilter: {
    data1: true,
    data2: false,
    data3: true,
    data4: false
  }
};
const specs = {
  // you can change separator by using configure({ separator: /pattern/ })
  "a.b.c.d.e.f": [$set, 100],
  "a.b.c.d.e": [$set, "newProp", 100],
  arrayPush: [$push, 1, 2, 3, 4, 5],
  objMerge: [$assign, { age: 20 }, { school: "A" }],
  // using obj method as modifier
  sqrt(x) {
    return Math.sqrt(x);
  },
  // toggle property itself
  toggleMe: [$toggle],
  // toggle child properties
  toggleMyProp: [$toggle, "done", "completed"],
  unsetMyProp: [$unset, "data1", "data2"],
  removeSecond: [$splice, 1, 1],
  // remove array items by its value
  removeAppleAndBanana: [$remove, "Apple", "Banana"],
  // using sub spec to update all array items
  // sub spec syntax [spec]
  // spec can be [action, ...args] or spec tree { a: {  b: ....} }
  doubleItems: [[x => x * 2]],
  // use action name instead of function
  swapItems: ["swap", 0, 1],
  // using sub spec to update all obj values
  increaseProps: [[x => x + 1]],
  removeByIndexes: ["removeAt", 3, 1],
  batchProcessing: ["batch", ["set", "name", "Peter"], ["set", "age", 20]],
  pipeProcessing: ["batch", x => x.toUpperCase(), x => x + " WORLD!!!"],
  //  apply sub spec for only odd numbers
  doubleOddNumbers: [[x => x * 2], x => x % 2],
  parentNode: {
    // remove childNode its self from parentNode
    childNode: ["unset"]
  },
  // remove item at index 1 from parentNodes array
  parentNodes: {
    1: ["unset"]
  },
  updateTree: {
    // using conditional spec to update all nodes which has text prop, exclude all data nodes
    "?": [node => node && node.text, ["set", "done", true]],
    // do same thing with pattern matching
    "?/text/i": ["set", "deleted", true],
    children: {
      // using diff spec for each node
      "?"(node, prop) {
        if (node && node.text) {
          return prop % 2 === 0
            ? ["set", "isEven", true]
            : ["set", "isOdd", true];
        }
        return undefined;
      }
    }
  },
  usingIfToUpdate: [
    "if",
    x => x % 2 === 0,
    ["set", "isEven", true],
    ["set", "isOdd", true]
  ],
  usingUnlessToUpdate: [
    "unless",
    x => x.dataLoaded,
    ["set", "text", "loading..."]
  ],
  usingSwitchToUpdate1: [
    "switch",
    {
      1: ["set", "one"],
      2: ["set", "two"],
      default: ["set", "other"]
    }
  ],
  usingSwitchToUpdate2: [
    "switch",
    x => (x.value ? "male" : "female"),
    {
      male: ["set", "sex", "male"],
      default: ["set", "sex", "female"]
    }
  ],
  usingFilter: ["filter", x % 2 === 0],
  unsetWithFilter: ["unset", (value, key) => !!value]
};
const result = update(original, specs);
expect(result).not.toBe(original);
expect(result).toEqual({
  a: {
    b: {
      c: {
        d: {
          e: {
            f: 100,
            newProp: 100
          }
        }
      }
    }
  },
  arrayPush: [1, 2, 3, 4, 5],
  objMerge: {
    name: "Peter",
    age: 20,
    school: "A"
  },
  toggleMe: true,
  toggleMyProp: {
    done: true,
    completed: false
  },
  unsetMyProp: {},
  sqrt: 10,
  removeSecond: [1, 3, 4],
  removeAppleAndBanana: ["Orange"],
  doubleItems: [2, 4, 6, 8, 10, 12, 14, 16],
  swapItems: ["right", "left"],
  increaseProps: {
    one: 2,
    two: 3,
    three: 4
  },
  removeByIndexes: [1, 3],
  batchProcessing: {
    name: "Peter",
    age: 20
  },
  pipeProcessing: "HELLO WORLD!!!",
  doubleOddNumbers: [2, 2, 6, 4],
  parentNode: {},
  parentNodes: [{ id: 0 }],
  updateTree: {
    text: "root",
    children: [
      {
        text: "child 1",
        done: true,
        deleted: true,
        isEven: true,
        data: {},
        children: [
          { text: "child 1.1", done: true, deleted: true, isEven: true }
        ]
      },
      {
        text: "child 2",
        done: true,
        deleted: true,
        isOdd: true,
        data: {},
        children: [
          { text: "child 2.1", done: true, deleted: true, isEven: true },
          { text: "child 2.2", done: true, deleted: true, isOdd: true }
        ]
      }
    ]
  },
  usingIfToUpdate: {
    value: 1,
    isOdd: true
  },
  usingUnlessToUpdate: {
    dataLoaded: false,
    text: "loading..."
  },
  usingSwitchToUpdate1: "one",
  usingSwitchToUpdate2: {
    value: true,
    sex: "male"
  },
  usingFilter: [2, 4],
  unsetWithFilter: {
    data2: false,
    data4: false
  }
});

Typescript support

immhelper.d.ts

declare namespace ImmHelper {
    // tuple [selector, action, ...args]
    type UpdateSpec<T> = [(model: T) => any, string | Function, ...any[]];
    interface Update {
        <T>(model: T, ...specs: UpdateSpec<T>[]): T;

        default: ImmHelper.Update;
    }
}

declare var updatePath: ImmHelper.Update;
export = updatePath;

Usages

/// <reference path="./immhelper.d.ts"/>
import { updatePath, $push, $set } from "immhelper";
const state = {
  a: {
    b: {
      c: []
    }
  }
};
const newState1 = updatePath(
  state,
  [x => x.a.b.c, $push, 1, 2, 3],
  [x => x.a.b, $set, "newProp", 100]
);
// this is shorter way but there are some limitations
// 1. Do not try to get prop value from obj, using originalState to get value instead
// 2. Only mutating actions are allowed
// 3. Mutating actions must be defined by using define(actionName, func) or define({ actionName: func })
const newState2 = updatePath(
  state,
  x => x.a.b.c.push(1, 2, 3),
  x => (x.a.b.newProp = 100)
);
console.log(newState1, newState2);

Mutating actions

[$push, ...items]
['push', ...items]
actions.push(target, ...items)
actions.$push(target, ...items)

push() all the items in array on the target

[$pop]
['pop']
actions.pop(target)
actions.$pop(target)

[$unshift, ...items]
['unshift', ...items]
actions.unshift(target, ...items)
actions.$unshift(target, ...items)

unshift() all the items in array on the target.

[$splice, index, count, ...items]
['splice', index, count, ...items]
actions.splice(target, index, count, ...items)
actions.$splice(target, index, count, ...items)

splice() remove item at specified index and push new items at there.

[$remove, ...items]
['remove', ...items]
actions.remove(target, ...items)
actions.$remove(target, ...items)

remove specified items from target array

[$removeAt, ...indexes]
['removeAt', ...indexes]
actions.removeAt(target, ...indexes)
actions.$removeAt(target, ...indexes)

remove items from array by indexes

[$set, value]
['set', value]
actions.set(target, value)
actions.$pop(target, value)

replace the target entirely.

[$set, prop, value]
['set', prop, value]
actions.set(target, prop, value)
actions.$set(target, prop, value)

set value for specified prop of the target

[$toggle]
['toggle']
actions.toggle(target)
actions.$toggle(target)

toggle target's value.

[$toggle, ...props]
['toggle', ...props]
actions.toggle(target, ...props)
actions.$toggle(target, ...props)

toggle all prop values of the target

[$unset, ...props]
['unset', ...props]
actions.unset(target, ...props)
actions.$unset(target, ...props)

remove keys from the target object or set undefined for array indexes

[$assign, ...objects]
['assign' ...objects]
actions.assign(target ...objects)
actions.$assign(target, ...objects)

copy the values of all enumerable own properties from one or more source objects to a target object

define(actionName, actionFunc, disableAutoClone)

define new mutating action, if disableAutoClone = true, original value will be passed instead of cloned value. Set disableAutoClone = true if you want to handle value manually, return original value if no change needed.

define("removeAt", function(originalArray, index) {
  // nothing to remove
  if (index >= originalArray.length) return originalArray;
  const newArray = originalArray.slice();
  newArray.splice(index, 1);
  return newArray;
}, true);

Special use cases

import { without, compact, zip } from "lodash";
import { define, updatePath } from "immhelper";

define({
  "+"(current, value) {
    return current + value;
  },
  "-"(current, value) {
    return current - value;
  },
  without,
  compact,
  zip
});

const state = { counter: 0 };

updatePath(state, [x => x.counter, "+", 1]);
updatePath(state, [x => x.counter, "-", 1]);

Define custom mutating actions

import { define, $set } from "immhelper";
define({
  "=": $set,
  addOne(currentValue, ...args) {
    return currentValue + 1;
  }
});
You might also like...
VueJS project for puling countries from rest api and display them

Live Demo https://shafeequeom.github.io/vue-world/ Run Project Locally 1. git cl

One component to link them all 🔗

VueLink - One component to link them all! Lightweight wrapper component for external and vue-router links. 🔥 Features Tiny functional component SSR-s

Scope IDs to an element by rewriting them to be globally unique

element-scope-ids - scope IDs to an element by rewriting them to be globally unique

Vue component provides textarea with automatically adjustable height and without any wrappers and dependencies

vue-textarea-autosize The plugin for Vue.js provides the Vue component implements textarea with automatically adjustable height and without any wrappe

You can with this app get about of countries. And you can search any countries and get about. Or search by region/zone.
You can with this app get about of countries. And you can search any countries and get about. Or search by region/zone.

You can with this app get about of countries. And you can search any countries and get about. Or search by region/zone.

Vue.js 2.x plugin that helps to use id-related attributes with no side-effect

VueUniqIds A Vue.js plugin that helps to use id-related attributes with no side-effect It is a trend to use components. Components are cool, they are

Vue component, that helps to make sticky effects
Vue component, that helps to make sticky effects

vue-sticker Vue component, that helps to make sticky effects Installation # install component npm install --save-dev vue-sticker using like local comp

Plugin helps to add resize params like width, height etc. to image url

Plugin helps to add resize params like width, height etc. to image url. Compatible with Vue 3.x. NOTE: Plugin doesn't resize image. Plugin only helps to format url to resize server with comfortable way.

A Vue.js directive helps track visible elements.

vue-visibility-track A Vue.js directive helps track elements's visibility. Installing Using npm: npm install --save vue-visibility-track Using yarn: y

Comments
  • add update-immutable to benchmark

    add update-immutable to benchmark

    • update-immutable is almost a drop-in replacement for the original react update(), and for immutability-helper
    • https://www.npmjs.com/package/update-immutable
    opened by hoytech 0
Owner
null
OSI helps you to track your all open-source Internships and Program in a single place ⚡

OSI helps you to track your all open-source Internships and Program in a single place ⚡

Rohan kumar 39 Jan 2, 2023
This is a lightweight plugin for TouchEvent in mobile without any dependency, which removes the 300ms delay from clicks.

This is a lightweight plugin for TouchEvent in mobile without any dependency, which removes the 300ms delay from clicks.

MaiSCRM 0 Sep 7, 2020
🚀 Install and update Among Us mods with a single click

English • Deutsch Among Us Mod Manager Install and update 6 Among Us mods with a single click* *on PC. Only the Steam version is tested, but other one

Moritz Ruth 12 Feb 21, 2022
A Vue.js browser-update plugin

x-browser-update A Vue.js browser-update plugin. Example # install dependencies npm install # serve with hot reload at localhost:8080 npm run dev Usa

葡萄干@吐鲁番 25 Jan 19, 2022
Add,Update,List Products with VueJs 🔰

intro ScreenShots ?? Project setup npm install Compiles and hot-reloads for development npm run serve Compiles and minifies for production npm run b

kensriii 4 Jun 12, 2022
Matteo Bruni 4.6k Dec 30, 2022
Scan your vaccination, test and recovery certificates in QR code representation and save them to your Apple Wallet

This web application offers the possibility to scan the EU-wide vaccination, test and recovery certificates (namely EU Digital COVID Certificate) as Q

Philipp Trenz 140 Dec 13, 2022
A platform collecting facts --data and evidence-- and presenting them AS IT IS in the useful way. No prejudice!

FactFinder.app @factfinderbot " Only Facts We Trust, No Prejudice! " Live (Release): https://factfinder.app Preview (Dev): https://fact-finder.vercel.

Kao.Geek 10 Aug 30, 2021
Design and visualize microtonal scales and play them in your web browser

Design and visualize microtonal scales and play them in your web browser. Export your scales for use with VST instruments. Convert Scala files to various tuning formats.

Xenharmonic Developers 22 Dec 31, 2022