immutability-helper

A drop-in replacement for react-addons-update that mutates a copy of your data without touching the original, using a concise MongoDB-inspired command syntax.

Library
npm
v3.1.1
5,182stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
44/100Fair
Development Activity0
Maintenance20
Community56
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
77/100Good
Architecture78
Code Quality88
Innovation55
Learning Curve85

immutability-helper gives JavaScript and TypeScript projects a small, focused API for producing updated copies of deeply nested data structures without hand-writing chains of Object.assign or spread operators. Instead of manually cloning every level of a nested object to change one field, you describe the change with a spec object built from $-prefixed commands ($set, $push, $merge, $splice, and more), and the library figures out which parts of the structure actually need to be copied.

The library grew out of Facebook’s original react-addons-update and keeps that same command syntax, which makes it a near drop-in replacement for anyone migrating off the deprecated React addon. It preserves referential equality wherever nothing changed, which matters for React’s shouldComponentUpdate and other reference-equality-based optimizations — unaffected branches of your object tree keep their original references so consumers can skip re-rendering.

Beyond the built-in commands, it exposes an extend() function and a Context class so teams can register custom commands or maintain an isolated command registry per module, rather than mutating a single global instance. The whole implementation lives in one TypeScript file with a fully generic type system that infers the correct spec shape (array spec, map spec, set spec, or object spec) from the target’s type, giving accurate autocomplete and compile-time checks for update specs on arbitrarily nested data.

What You Get

  • A concise update(target, spec) function supporting $set, $push, $unshift, $splice, $merge, $unset, $toggle, $apply, $add, and $remove commands
  • Referential-equality preservation — object branches unaffected by an update keep their original object reference
  • A Context class for creating an isolated command registry, so custom commands in one module don’t leak into another’s
  • An extend() API for registering project-specific commands (e.g. $addtax) alongside the built-in ones
  • Full TypeScript definitions that infer the correct spec shape (array, Map, Set, or object) from the target type, including support for custom command types via CustomCommands<T>
  • First-class support for updating Map and Set values via $add/$remove, not just plain objects and arrays

Common Use Cases

  • Updating deeply nested React component state without writing manual Object.assign chains for each level
  • Writing Redux or Redux Toolkit reducers that need to return new state objects while preserving unaffected slices
  • Implementing shouldComponentUpdate or React.memo comparisons that rely on reference equality to skip re-renders
  • Migrating legacy codebases off the deprecated react-addons-update package with minimal syntax changes
  • Manipulating collections (arrays, Maps, Sets) immutably in any state-management layer, not just React

Under The Hood

Architecture The entire library is implemented in a single index.ts file structured around one exported Context class: a commands map seeded with the built-in $-prefixed handlers, and an update(object, spec) method that walks the spec’s keys, dispatches to a matching command function when the key starts with $, or recurses into update() for the corresponding nested value otherwise. A module-level defaultContext instance backs the default update/extend/isEquals exports, while consumers who need isolated custom commands can instantiate their own Context. The core invariant the architecture protects is copy-on-write: a nextObject === object check gates every mutation path, so a shallow copy() (via Object.assign, new Map(...), or new Set(...) depending on target type) is only made the first time a branch actually changes, letting unaffected object references pass through untouched.

Tech Stack The package is authored directly in TypeScript (index.ts) and compiled to index.js plus index.d.ts via tsc per its package.json build script, with no runtime dependencies at all — Object.assign, Object.getOwnPropertySymbols, and native Map/Set do all the heavy lifting, with small polyfill-style fallbacks (assign, getAllKeys) for older environments lacking those APIs. Development tooling is jest with ts-jest for running the TypeScript test suite directly, tslint for linting (via tslint.json), and rimraf for cleaning build artifacts between steps; there is no bundler since the package ships a single compiled file plus type declarations.

Code Quality The test suite (test.ts, over 600 lines) exercises every built-in command, custom extend() behavior, Context isolation, and edge cases like frozen source objects and invalid specs, and the jest config enforces a 100% coverage threshold across branches, functions, lines, and statements — meaning the CI-gated npm test script fails outright if any code path goes untested. Error handling is explicit: an internal invariant() helper throws descriptive, developer-facing messages (e.g. “expected target of $push to be an array”) for malformed specs rather than silently producing wrong output, and those messages are stripped to a generic “Invariant failed” in production builds to save bundle size. TypeScript’s structural generics are used throughout Spec<T, C> to make invalid spec shapes a compile error rather than a runtime surprise.

What Makes It Unique Rather than a general deep-clone or a full immutable-collection library like Immutable.js, immutability-helper’s whole value proposition is a minimal, declarative diffing/copy strategy expressed as a plain object spec, which stays close to the shape of the data being updated and avoids the API-conversion cost of adopting a whole new data structure family. Its extensibility model — a per-instance Context plus a global extend() — lets teams add domain-specific update commands without forking the library, a pattern most comparable spread/merge helper libraries don’t offer.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search