Create immutable state by mutating the current one with structural sharing
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Advanced patch tracking system for implementing undo/redo, debugging, and state synchronization features. The patches system allows you to track, serialize, and replay state changes with fine-grained detail.
Enables the patches plugin, which is required for all patch-related functionality.
/**
* Enable the patches plugin for tracking changes as patches
* Must be called before using patch-related functions
*/
function enablePatches(): void;This function must be called once before using produceWithPatches, applyPatches, or patch listeners with produce.
Applies an array of Immer patches to the first argument, creating a new state with the patches applied.
/**
* Apply an array of Immer patches to the first argument
* @param base - The object to apply patches to
* @param patches - Array of patch objects describing changes
* @returns New state with patches applied
*/
function applyPatches<T>(base: T, patches: readonly Patch[]): T;Usage Examples:
import { enablePatches, produceWithPatches, applyPatches } from "immer";
// Enable patches functionality
enablePatches();
const baseState = {
user: { name: "John", age: 30 },
todos: ["Learn Immer", "Use patches"]
};
// Generate patches
const [nextState, patches, inversePatches] = produceWithPatches(baseState, draft => {
draft.user.age = 31;
draft.todos.push("Master patches");
draft.user.email = "john@example.com";
});
console.log(patches);
// [
// { op: "replace", path: ["user", "age"], value: 31 },
// { op: "add", path: ["todos", 2], value: "Master patches" },
// { op: "add", path: ["user", "email"], value: "john@example.com" }
// ]
// Apply patches to recreate the same transformation
const recreatedState = applyPatches(baseState, patches);
console.log(JSON.stringify(recreatedState) === JSON.stringify(nextState)); // true
// Apply patches to different base state
const differentBase = {
user: { name: "Jane", age: 25 },
todos: ["Task 1"]
};
const transformedDifferent = applyPatches(differentBase, patches);
console.log(transformedDifferent);
// {
// user: { name: "Jane", age: 31, email: "john@example.com" },
// todos: ["Task 1", undefined, "Master patches"]
// }
// Apply inverse patches for undo functionality
const revertedState = applyPatches(nextState, inversePatches);
console.log(JSON.stringify(revertedState) === JSON.stringify(baseState)); // truePatch listeners can be used with produce and finishDraft to track changes without using produceWithPatches.
import { produce, finishDraft, createDraft, enablePatches } from "immer";
enablePatches();
const state = { count: 0, items: [] as string[] };
// Using patch listener with produce
const updatedState = produce(
state,
draft => {
draft.count += 1;
draft.items.push("new item");
},
(patches, inversePatches) => {
console.log("Changes made:", patches);
console.log("To undo:", inversePatches);
}
);
// Using patch listener with finishDraft
const draft = createDraft(state);
draft.count = 10;
draft.items.push("manual item");
const result = finishDraft(draft, (patches, inversePatches) => {
// Log or store patches for later use
console.log("Manual draft changes:", patches);
});interface Patch {
/** The type of operation: add, remove, or replace */
op: "replace" | "remove" | "add";
/** Path to the changed value as array of keys/indices */
path: (string | number)[];
/** The new value (undefined for remove operations) */
value?: any;
}
type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void;Add Operations:
// Adding array element
{ op: "add", path: ["items", 2], value: "new item" }
// Adding object property
{ op: "add", path: ["user", "email"], value: "user@example.com" }
// Adding nested property
{ op: "add", path: ["config", "settings", "theme"], value: "dark" }Replace Operations:
// Replacing primitive value
{ op: "replace", path: ["count"], value: 42 }
// Replacing array element
{ op: "replace", path: ["items", 0], value: "updated item" }
// Replacing nested object property
{ op: "replace", path: ["user", "profile", "name"], value: "New Name" }Remove Operations:
// Removing array element
{ op: "remove", path: ["items", 1] }
// Removing object property
{ op: "remove", path: ["user", "temporaryData"] }
// Removing nested property
{ op: "remove", path: ["config", "deprecated", "oldSetting"] }import { enablePatches, produce, applyPatches, Patch } from "immer";
enablePatches();
class UndoRedoStore<T> {
private history: T[] = [];
private patches: Patch[][] = [];
private inversePatches: Patch[][] = [];
private currentIndex = -1;
constructor(private initialState: T) {
this.history.push(initialState);
this.currentIndex = 0;
}
get current(): T {
return this.history[this.currentIndex];
}
update(updater: (draft: any) => void): T {
const [nextState, patches, inversePatches] = produceWithPatches(
this.current,
updater
);
// Remove any future history when making new changes
this.history = this.history.slice(0, this.currentIndex + 1);
this.patches = this.patches.slice(0, this.currentIndex);
this.inversePatches = this.inversePatches.slice(0, this.currentIndex);
// Add new state and patches
this.history.push(nextState);
this.patches.push(patches);
this.inversePatches.push(inversePatches);
this.currentIndex++;
return nextState;
}
undo(): T | null {
if (this.currentIndex <= 0) return null;
const patches = this.inversePatches[this.currentIndex - 1];
const prevState = applyPatches(this.current, patches);
this.currentIndex--;
return prevState;
}
redo(): T | null {
if (this.currentIndex >= this.history.length - 1) return null;
const patches = this.patches[this.currentIndex];
const nextState = applyPatches(this.current, patches);
this.currentIndex++;
return nextState;
}
canUndo(): boolean {
return this.currentIndex > 0;
}
canRedo(): boolean {
return this.currentIndex < this.history.length - 1;
}
}
// Usage
const store = new UndoRedoStore({ todos: [], count: 0 });
// Make changes
store.update(draft => {
draft.todos.push("Task 1");
draft.count = 1;
});
store.update(draft => {
draft.todos.push("Task 2");
draft.count = 2;
});
console.log(store.current); // { todos: ["Task 1", "Task 2"], count: 2 }
// Undo
store.undo();
console.log(store.current); // { todos: ["Task 1"], count: 1 }
// Redo
store.redo();
console.log(store.current); // { todos: ["Task 1", "Task 2"], count: 2 }import { enablePatches, produceWithPatches, applyPatches, Patch } from "immer";
enablePatches();
class StateSynchronizer<T> {
private listeners: Array<(patches: Patch[]) => void> = [];
constructor(private state: T) {}
// Subscribe to state changes
subscribe(listener: (patches: Patch[]) => void): () => void {
this.listeners.push(listener);
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) this.listeners.splice(index, 1);
};
}
// Update local state and notify listeners
update(updater: (draft: any) => void): T {
const [nextState, patches] = produceWithPatches(this.state, updater);
if (patches.length > 0) {
this.state = nextState;
this.listeners.forEach(listener => listener(patches));
}
return nextState;
}
// Apply patches from remote source
applyRemotePatches(patches: Patch[]): T {
this.state = applyPatches(this.state, patches);
return this.state;
}
getCurrentState(): T {
return this.state;
}
}
// Usage for real-time collaboration
const localSync = new StateSynchronizer({
document: { title: "Shared Doc", content: "" },
users: [] as string[]
});
// Send patches to remote when local changes occur
const unsubscribe = localSync.subscribe(patches => {
// In real app, send patches to server/other clients
console.log("Sending patches to remote:", patches);
// websocket.send(JSON.stringify({ type: 'patches', patches }));
});
// Apply changes locally
localSync.update(draft => {
draft.document.title = "Collaborative Document";
draft.users.push("Alice");
});
// Simulate receiving remote patches
const remotePatches: Patch[] = [
{ op: "replace", path: ["document", "content"], value: "Hello world!" },
{ op: "add", path: ["users", 1], value: "Bob" }
];
localSync.applyRemotePatches(remotePatches);
console.log(localSync.getCurrentState());
// {
// document: { title: "Collaborative Document", content: "Hello world!" },
// users: ["Alice", "Bob"]
// }import { enablePatches, produceWithPatches, applyPatches, Patch } from "immer";
enablePatches();
class PatchLogger<T> {
private patchHistory: Array<{ timestamp: number; patches: Patch[] }> = [];
constructor(private initialState: T) {}
// Apply update and log patches
update(updater: (draft: any) => void): T {
const [nextState, patches] = produceWithPatches(this.initialState, updater);
if (patches.length > 0) {
// Store patches with timestamp
this.patchHistory.push({
timestamp: Date.now(),
patches: patches
});
this.initialState = nextState;
}
return nextState;
}
// Serialize patch history to JSON
exportHistory(): string {
return JSON.stringify({
initialState: this.initialState,
patches: this.patchHistory
});
}
// Restore from serialized patch history
static fromHistory<T>(serialized: string): { state: T; logger: PatchLogger<T> } {
const { initialState, patches } = JSON.parse(serialized);
// Replay all patches to reconstruct current state
let currentState = initialState;
for (const entry of patches) {
currentState = applyPatches(currentState, entry.patches);
}
const logger = new PatchLogger(currentState);
logger.patchHistory = patches;
return { state: currentState, logger };
}
// Get patches within time range
getPatchesBetween(startTime: number, endTime: number): Patch[] {
return this.patchHistory
.filter(entry => entry.timestamp >= startTime && entry.timestamp <= endTime)
.flatMap(entry => entry.patches);
}
}
// Usage
const logger = new PatchLogger({ data: [], version: 1 });
logger.update(draft => {
draft.data.push("item1");
});
logger.update(draft => {
draft.version = 2;
draft.data.push("item2");
});
// Export and restore
const serialized = logger.exportHistory();
const { state, logger: restoredLogger } = PatchLogger.fromHistory(serialized);
console.log(state); // { data: ["item1", "item2"], version: 2 }The patches system provides powerful capabilities for change tracking, state synchronization, and building complex state management patterns with Immer.