Mutate a copy of data without changing the original source
npx @tessl/cli install tessl/npm-immutability-helper@3.1.0Immutability Helper provides a MongoDB-inspired syntax for creating modified copies of JavaScript data structures without mutating the original data. It serves as a drop-in replacement for React's deprecated react-addons-update, offering powerful immutable update operations with shallow copying for performance.
npm install immutability-helperimport update from "immutability-helper";
import { Context, extend, isEquals, invariant } from "immutability-helper";For CommonJS:
const update = require("immutability-helper");
const { Context, extend, isEquals, invariant } = require("immutability-helper");import update from "immutability-helper";
// Update arrays
const initialArray = [1, 2, 3];
const newArray = update(initialArray, { $push: [4] }); // [1, 2, 3, 4]
// Update objects
const obj = { a: 5, b: 3 };
const newObj = update(obj, { b: { $set: 6 } }); // { a: 5, b: 6 }
// Nested updates
const collection = [1, 2, { a: [12, 17, 15] }];
const newCollection = update(collection, {
2: { a: { $splice: [[1, 1, 13, 14]] } }
}); // [1, 2, { a: [12, 13, 14, 15] }]
// Function-based updates
const updated = update(obj, { b: { $apply: x => x * 2 } });Immutability Helper is built around several core concepts:
update(object, spec) function that applies specifications to data$-prefixed commands for different operation typesContext class for isolated environments with custom commandsCore operations for modifying arrays including push, unshift, and splice operations.
// Push items to end of array
{ $push: ReadonlyArray<T> }
// Add items to beginning of array
{ $unshift: ReadonlyArray<T> }
// Perform splice operations
{ $splice: ReadonlyArray<[number, number?] | [number, number, ...T[]]> }Operations for modifying object properties including set, merge, toggle, and unset.
// Replace target entirely
{ $set: T }
// Shallow merge properties
{ $merge: Partial<T> }
// Toggle boolean fields
{ $toggle: ReadonlyArray<keyof T> }
// Remove properties
{ $unset: ReadonlyArray<keyof T> }Specialized operations for ES6 Map and Set data structures.
// Add entries to Map or Set
{ $add: ReadonlyArray<[K, V]> | ReadonlyArray<T> }
// Remove entries from Map or Set
{ $remove: ReadonlyArray<K> }Function-based operations for complex transformations and custom logic.
// Apply function to current value
{ $apply: (v: T) => T }
// Shorthand: pass function directly
(v: T) => TAdvanced features for creating isolated contexts and adding custom commands.
class Context {
constructor();
extend<T>(directive: string, fn: (param: any, old: T) => T): void;
update<T, C extends CustomCommands<object> = never>(object: T, $spec: Spec<T, C>): T;
get isEquals(): (x: any, y: any) => boolean;
set isEquals(value: (x: any, y: any) => boolean);
}
function extend<T>(directive: string, fn: (param: any, old: T) => T): void;Important: The update function only works for data properties, not for accessor properties defined with Object.defineProperty. It does not see accessor properties and may create shadowing data properties which could break application logic depending on setter side effects. Therefore update should only be used on plain data objects that contain only data properties as descendants.
// Main update function type
function update<T, C extends CustomCommands<object> = never>(
object: T,
$spec: Spec<T, C>
): T;
// Specification type for updates
type Spec<T, C extends CustomCommands<object> = never> =
| (T extends (Array<infer U> | ReadonlyArray<infer U>) ? ArraySpec<U, C> :
T extends (Map<infer K, infer V> | ReadonlyMap<infer K, infer V>) ? MapSpec<K, V, C> :
T extends (Set<infer X> | ReadonlySet<infer X>) ? SetSpec<X> :
T extends object ? ObjectSpec<T, C> :
never)
| { $set: T }
| { $apply: (v: T) => T }
| ((v: T) => T)
| (C extends CustomCommands<infer O> ? O : never);
// Custom commands type brand
type CustomCommands<T> = T & { __noInferenceCustomCommandsBrand: any };
// Specialized spec types
type ArraySpec<T, C extends CustomCommands<object>> =
| { $push: ReadonlyArray<T> }
| { $unshift: ReadonlyArray<T> }
| { $splice: ReadonlyArray<[number, number?] | [number, number, ...T[]]> }
| { [index: string]: Spec<T, C> };
type MapSpec<K, V, C extends CustomCommands<object>> =
| { $add: ReadonlyArray<[K, V]> }
| { $remove: ReadonlyArray<K> }
| { [key: string]: Spec<V, C> };
type SetSpec<T> =
| { $add: ReadonlyArray<T> }
| { $remove: ReadonlyArray<T> };
type ObjectSpec<T, C extends CustomCommands<object>> =
| { $toggle: ReadonlyArray<keyof T> }
| { $unset: ReadonlyArray<keyof T> }
| { $merge: Partial<T> }
| { [K in keyof T]?: Spec<T[K], C> };
// Utility functions
/**
* Throws an error if condition is false
* @param condition - Boolean condition to check
* @param message - Function that returns error message
*/
function invariant(condition: boolean, message: () => string): void;
/**
* Default equality function used for change detection
* Can be overridden via Context.isEquals
*/
const isEquals: (x: any, y: any) => boolean;