Mutate a copy of data without changing the original source
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Advanced features for creating isolated contexts and adding custom commands. The extension system allows you to add your own commands to extend immutability-helper's functionality.
The Context class provides an isolated environment for update operations with custom commands and equality functions.
class Context {
/**
* Create a new Context instance with isolated command set
*/
constructor();
/**
* Add a custom command to this context
* @param directive - Command name (with $ prefix)
* @param fn - Function that implements the command
*/
extend<T>(directive: string, fn: (param: any, old: T) => T): void;
/**
* Update function for this context
* @param object - Object to update
* @param spec - Update specification
* @returns Updated object
*/
update<T, C extends CustomCommands<object> = never>(
object: T,
$spec: Spec<T, C>
): T;
/**
* Get the equality function used for change detection
*/
get isEquals(): (x: any, y: any) => boolean;
/**
* Set a custom equality function for change detection
*/
set isEquals(value: (x: any, y: any) => boolean);
}Usage Examples:
import { Context } from "immutability-helper";
// Create isolated context
const myContext = new Context();
// Add custom command
myContext.extend('$addTax', function(tax, original) {
return original + (tax * original);
});
// Use custom context
const price = { amount: 100 };
const withTax = myContext.update(price, {
amount: { $addTax: 0.2 }
});
// Result: { amount: 120 }
// Custom equality function
myContext.isEquals = (a, b) => {
// Custom deep equality logic
return JSON.stringify(a) === JSON.stringify(b);
};Add custom commands to the default global context.
/**
* Add a custom command to the global context
* @param directive - Command name (with $ prefix)
* @param fn - Function that implements the command
*/
function extend<T>(directive: string, fn: (param: any, old: T) => T): void;Usage Examples:
import update, { extend } from "immutability-helper";
// Add global custom command
extend('$addTax', function(tax, original) {
return original + (tax * original);
});
// Use globally
const result = update({ price: 100 }, {
price: { $addTax: 0.15 }
});
// Result: { price: 115 }
// String manipulation command
extend('$capitalize', function(_, original) {
return typeof original === 'string'
? original.charAt(0).toUpperCase() + original.slice(1).toLowerCase()
: original;
});
const text = { title: 'hello world' };
const capitalized = update(text, {
title: { $capitalize: null }
});
// Result: { title: 'Hello world' }import { extend } from "immutability-helper";
// Add mathematical operations
extend('$multiply', (factor, original) => original * factor);
extend('$power', (exponent, original) => Math.pow(original, exponent));
extend('$round', (decimals, original) => {
const multiplier = Math.pow(10, decimals || 0);
return Math.round(original * multiplier) / multiplier;
});
const data = { value: 3.14159 };
const processed = update(data, {
value: { $round: 2 }
});
// Result: { value: 3.14 }import update, { extend } from "immutability-helper";
// Custom array operations
extend('$shuffle', (_, original) => {
if (!Array.isArray(original)) return original;
const shuffled = [...original];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
});
extend('$take', (count, original) => {
return Array.isArray(original) ? original.slice(0, count) : original;
});
const numbers = { list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] };
const taken = update(numbers, {
list: { $take: 3 }
});
// Result: { list: [1, 2, 3] }import { extend } from "immutability-helper";
// Conditional update command
extend('$if', (condition, original) => {
const { test, then: thenSpec, else: elseSpec } = condition;
const shouldUpdate = typeof test === 'function' ? test(original) : test;
if (shouldUpdate && thenSpec) {
return update(original, thenSpec);
} else if (!shouldUpdate && elseSpec) {
return update(original, elseSpec);
}
return original;
});
const user = { age: 17, status: 'minor' };
const updated = update(user, {
$if: {
test: (obj) => obj.age >= 18,
then: { status: { $set: 'adult' } },
else: { status: { $set: 'minor' } }
}
});Autovivification is the automatic creation of new arrays and objects when needed. JavaScript doesn't have this feature natively, which makes deep nested updates challenging. These custom commands solve this problem:
import update, { extend } from "immutability-helper";
// Auto-create missing nested structures
extend('$auto', function(value, object) {
return object ? update(object, value) : update({}, value);
});
extend('$autoArray', function(value, object) {
return object ? update(object, value) : update([], value);
});
// Usage example - building deep structures from empty state
const state = {};
const result = update(state, {
users: { $autoArray: {
0: { $auto: {
name: { $set: 'Alice' },
posts: { $autoArray: { $push: ['Hello World'] } }
}}
}}
});
// Result: { users: [{ name: 'Alice', posts: ['Hello World'] }] }
// Alternative approach without custom commands (manual autovivification)
const state2 = {};
const desiredState = {
foo: [{ bar: ['x', 'y', 'z'] }]
};
const manualResult = update(state2, {
foo: foo =>
update(foo || [], {
0: fooZero =>
update(fooZero || {}, {
bar: bar => update(bar || [], { $push: ["x", "y", "z"] })
})
})
});
// Result matches desiredStateCustom command functions receive three parameters:
type CommandFunction<T> = (
param: any, // The parameter passed to the command
nextObject: T, // The current object being updated
spec: any, // The full specification object
originalObject: T // The original object before any updates
) => T;Advanced Command Example:
extend('$increment', function(amount, nextObject, spec, originalObject) {
// param: amount to increment
// nextObject: current value during update chain
// spec: the full spec object containing $increment
// originalObject: the original value before any updates
if (typeof nextObject === 'number') {
return nextObject + (amount || 1);
}
return nextObject;
});For TypeScript users, you can create type-safe custom commands:
import update, { CustomCommands, Spec } from "immutability-helper";
// Define custom command types
interface MyCommands {
$addTax: number;
$capitalize: null;
}
// Create typed update function
function myUpdate<T>(object: T, spec: Spec<T, CustomCommands<MyCommands>>) {
return update(object, spec);
}
// Usage with full type safety
const result = myUpdate({ price: 100, name: 'product' }, {
price: { $addTax: 0.2 }, // TypeScript knows this expects a number
name: { $capitalize: null } // TypeScript knows this expects null
});invariant function for consistent error messagesimport { invariant } from "immutability-helper";
extend('$safeIncrement', function(amount, original) {
invariant(
typeof original === 'number',
() => `$safeIncrement expects a number, got ${typeof original}`
);
invariant(
typeof amount === 'number',
() => `$safeIncrement amount must be a number, got ${typeof amount}`
);
return original + amount;
});originalObject) for optimization decisions