### Importing produce Source: https://immerjs.github.io/immer/api Example of how to import the `produce` function from Immer. ```APIDOC ## Importing immer ### Description In most cases, the only thing you need to import from Immer is `produce`. ### Code Example ```javascript import { produce } from "immer" ``` ### Note In older versions, `produce` was also available as a default export (e.g. `import produce from "immer"`), but that is no longer the case to improve ecosystem compatibility. ``` -------------------------------- ### Base State Example Source: https://immerjs.github.io/immer Defines the initial immutable state for demonstration purposes. ```javascript const baseState = [ { title: "Learn TypeScript", done: true }, { title: "Try Immer", done: false } ] ``` -------------------------------- ### Immer Reducer Examples Source: https://immerjs.github.io/immer/return Demonstrates various ways to handle state updates within an Immer producer, including modifying the draft, returning the draft, returning new state, and incorrect patterns. ```javascript const userReducer = produce((draft, action) => { switch (action.type) { case "renameUser": // OK: we modify the current state draft.users[action.payload.id].name = action.payload.name return draft // same as just 'return' case "loadUsers": // OK: we return an entirely new state return action.payload case "adduser-1": // NOT OK: This doesn't do change the draft nor return a new state! // It doesn't modify the draft (it just redeclares it) // In fact, this just doesn't do anything at all draft = {users: [...draft.users, action.payload]} return case "adduser-2": // NOT OK: modifying draft *and* returning a new state draft.userCount += 1 return {users: [...draft.users, action.payload]} case "adduser-3": // OK: returning a new state. But, unnecessary complex and expensive return { userCount: draft.userCount + 1, users: [...draft.users, action.payload] } case "adduser-4": // OK: the immer way draft.userCount += 1 draft.users.push(action.payload) return } }) ``` -------------------------------- ### Example Immer Patches (Replace and Add) Source: https://immerjs.github.io/immer/patches Shows another set of Immer patches, featuring a 'replace' operation and an 'add' operation to an array. The 'path' property uses an array format. ```json [ {"op": "replace", "path": ["profile"], "value": {"name": "Noa", "age": 6}}, {"op": "add", "path": ["tags", 3], "value": "kiddo"} ] ``` -------------------------------- ### Generate State, Patches, and Inverse Patches with produceWithPatches Source: https://immerjs.github.io/immer/patches Use `produceWithPatches` to get the next state, patches, and inverse patches. This function supports currying and is ideal for building undo/redo features. ```javascript import {produceWithPatches} from "immer" const [nextState, patches, inversePatches] = produceWithPatches( { age: 33 }, draft => { draft.age++ } ) ``` ```json [ { "age": 34 }, [ { "op": "replace", "path": ["age"], "value": 34 } ], [ { "op": "replace", "path": ["age"], "value": 33 } ] ] ``` -------------------------------- ### Using Immer's `current` and `original` Source: https://immerjs.github.io/immer/current Demonstrates how to use `current` to get a snapshot of the draft state and `original` to access the original state within a `produce` function. Note that `current` provides a copy that won't reflect future changes to the draft. ```javascript const base = { x: 0 } const next = produce(base, draft => { draft.x++ const orig = original(draft) const copy = current(draft) console.log(orig.x) console.log(copy.x) setTimeout(() => { // this will execute after the produce has finished! console.log(orig.x) console.log(copy.x) }, 100) draft.x++ console.log(draft.x) }) console.log(next.x) // This will print // 0 (orig.x) // 1 (copy.x) // 2 (draft.x) // 2 (next.x) // 0 (after timeout, orig.x) // 1 (after timeout, copy.x) ``` -------------------------------- ### Example Immer Patches (Replace and Remove) Source: https://immerjs.github.io/immer/patches Illustrates a set of Immer patches, including a 'replace' operation on a nested object and a 'remove' operation on an array element. The 'path' property is an array. ```json [ { "op": "replace", "path": ["profile"], "value": {"name": "Veria", "age": 5} }, {"op": "remove", "path": ["tags", 3]} ] ``` -------------------------------- ### Curried Producer with Explicit State and Arguments Source: https://immerjs.github.io/immer/typescript Example of defining a curried producer with an explicit state generic and additional arguments. The types of additional arguments must be specified as a tuple. ```typescript const toggler = produce((draft, newState) => { draft.done = newState }) // typeof toggler = (state: Todo, newState: boolean) => Todo ``` -------------------------------- ### Performance Benefit Example Source: https://immerjs.github.io/immer/array-methods Illustrates the performance benefit of the Immer Array Methods Plugin. Without the plugin, many proxies are created. With the plugin, proxies are only created for elements that are actually mutated or match filtering predicates. ```javascript // Without plugin: ~3000+ proxy trap invocations // With plugin: ~10-20 proxy trap invocations const result = produce(largeState, draft => { const filtered = draft.items.filter(x => x.value > threshold) // Only items you mutate get proxied filtered.forEach(item => { item.processed = true }) }) ``` -------------------------------- ### Redux Reducer with Immer Source: https://immerjs.github.io/immer/example-setstate Wrap your Redux reducer with `produce` from Immer to safely mutate the state draft. This example shows handling 'toggle' and 'add' actions for a list of todos. ```javascript import {produce} from "immer" // Reducer with initial state const INITIAL_STATE = [ /* bunch of todos */ ] const todosReducer = produce((draft, action) => { switch (action.type) { case "toggle": const todo = draft.find(todo => todo.id === action.id) todo.done = !todo.done break case "add": draft.push({ id: action.id, title: "A new todo", done: false }) break default: break } }) ``` -------------------------------- ### Immer TypeScript Example Source: https://immerjs.github.io/immer/typescript Demonstrates how Immer's TypeScript typings handle `readonly` modifiers, allowing modification within the `produce` callback while maintaining immutability outside. ```typescript import {produce} from "immer" interface State { readonly x: number } // `x` cannot be modified here const state: State = { x: 0 } const newState = produce(state, draft => { // `x` can be modified here draft.x++ }) // `newState.x` cannot be modified here ``` -------------------------------- ### Enable Map and Set Support in Immer Source: https://immerjs.github.io/immer/installation To use Immer with native Map and Set collections, enable this feature once at your application's entry point. This example demonstrates updating a Map using Immer's produce function after enabling Map/Set support. ```javascript import {enableMapSet} from "immer" enableMapSet() // ...later import {produce} from "immer" const usersById_v1 = new Map([ ["michel", {name: "Michel Weststrate", country: "NL"}] ]) const usersById_v2 = produce(usersById_v1, draft => { draft.get("michel").country = "UK" }) expect(usersById_v1.get("michel").country).toBe("NL") expect(usersById_v2.get("michel").country).toBe("UK") ``` -------------------------------- ### Extract Original State with Immer's `original` Source: https://immerjs.github.io/immer/original Use the `original` function to get the original object from a proxied instance inside `produce`. It returns `undefined` for unproxied values. This is useful for strict equality checks, like searching for nodes in a tree. ```javascript import {original, produce} from "immer" const baseState = {users: [{name: "Richie"}]} const nextState = produce(baseState, draftState => { original(draftState.users) // is === baseState.users }) ``` -------------------------------- ### Map and Set Support: enableMapSet() Source: https://immerjs.github.io/immer/api Enables support for `Map` and `Set` collections. ```APIDOC ## `enableMapSet()` ### Description Enables support for `Map` and `Set` collections within Immer. ### Method `enableMapSet()` ### Usage Call this function once, typically at the start of your application, to enable `Map` and `Set` support. ``` -------------------------------- ### Using Primitive-Returning Array Methods Source: https://immerjs.github.io/immer/array-methods Demonstrates the usage of primitive-returning array methods like `findIndex`, `some`, and `every` within an Immer producer. These methods return primitive values and do not create draft proxies for elements. ```javascript const base = { items: [ {id: 1, active: true}, {id: 2, active: false} ] } const result = produce(base, draft => { const index = draft.items.findIndex(item => item.id === 2) const hasActive = draft.items.some(item => item.active) const allActive = draft.items.every(item => item.active) console.log(index) // 1 console.log(hasActive) // true console.log(allActive) // false }) ``` -------------------------------- ### Async Updates with createDraft and finishDraft Source: https://immerjs.github.io/immer/async Demonstrates using createDraft and finishDraft for async updates. Note: This is an anti-pattern; fetch data first, then draft. finishDraft can accept a patchListener. ```javascript import {createDraft, finishDraft} from "immer" const user = { name: "michel", todos: [] } const draft = createDraft(user) draft.todos = await (await window.fetch("http://host/" + draft.name)).json() const loadedUser = finishDraft(draft) ``` -------------------------------- ### Enable Immer Array Methods Plugin Source: https://immerjs.github.io/immer/array-methods Enable the Array Methods Plugin once at your application's entry point. This adds approximately 2KB to your bundle size. ```javascript import {enableArrayMethods} from "immer" enableArrayMethods() ``` -------------------------------- ### Enable and Use Patches with Immer Source: https://immerjs.github.io/immer/patches Enables patches and demonstrates producing state with changes, capturing patches and inverse patches, applying changes to a modified state, and reverting changes using inverse patches. Requires Immer version 6 or later. ```javascript import {produce, applyPatches} from "immer" // version 6 import { enablePatches} from "immer" enablePatches() let state = { name: "Micheal", age: 32 } // Let's assume the user is in a wizard, and we don't know whether // his changes should end up in the base state ultimately or not... let fork = state // all the changes the user made in the wizard let changes = [] // the inverse of all the changes made in the wizard let inverseChanges = [] fork = produce( fork, draft => { draft.age = 33 }, // The third argument to produce is a callback to which the patches will be fed (patches, inversePatches) => { changes.push(...patches) inverseChanges.push(...inversePatches) } ) // In the meantime, our original state is replaced, as, for example, // some changes were received from the server state = produce(state, draft => { draft.name = "Michel" }) // When the wizard finishes (successfully) we can replay the changes that were in the fork onto the *new* state! state = applyPatches(state, changes) // state now contains the changes from both code paths! expect(state).toEqual({ name: "Michel", // changed by the server age: 33 // changed by the wizard }) // Finally, even after finishing the wizard, the user might change his mind and undo his changes... state = applyPatches(state, inverseChanges) expect(state).toEqual({ name: "Michel", // Not reverted age: 32 // Reverted }) ``` -------------------------------- ### Basic state update with produce Source: https://immerjs.github.io/immer/produce Use `produce` to create a new state object with mutations applied to a draft. The original state is preserved. ```javascript import {produce} from "immer" const baseState = [ { title: "Learn TypeScript", done: true }, { title: "Try Immer", done: false } ] const nextState = produce(baseState, draftState => { draftState.push({title: "Tweet about it"}) draftState[1].done = true }) ``` -------------------------------- ### Advanced Patching: produceWithPatches Source: https://immerjs.github.io/immer/api Produces a new state and returns associated patches. ```APIDOC ## `produceWithPatches` ### Description Works the same as `produce`, but instead of just returning the produced object, it returns a tuple, consisting of `[result, patches, inversePatches]`. ### Method `produceWithPatches` ### Parameters - **baseState** (any) - The initial state. - **recipe** (function) - A function that receives a draft of the state and applies mutations. ### Request Example ```javascript import { produceWithPatches } from "immer" const baseState = { a: 1 }; const [nextState, patches, inversePatches] = produceWithPatches(baseState, draft => { draft.a = draft.a + 1; }); // nextState is { a: 2 } // patches is [{ op: "replace", path: ["a"], value: 2 }] // inversePatches is [{ op: "replace", path: ["a"], value: 1 }] ``` ### Response #### Success Response (200) - **result** (any) - The new immutable state. - **patches** (Array) - An array of patches describing the changes. - **inversePatches** (Array) - An array of patches describing how to undo the changes. ``` -------------------------------- ### useImmerReducer for Simplified Reducer Logic Source: https://immerjs.github.io/immer/example-setstate Utilize the `useImmerReducer` hook from the `use-immer` package to automatically apply Immer's `produce` to reducer functions. This simplifies state management with `useReducer`. ```javascript import React, { useCallback } from "react"; import { useImmerReducer } from "use-immer"; const TodoList = () => { const [todos, dispatch] = useImmerReducer( (draft, action) => { switch (action.type) { case "toggle": const todo = draft.find((todo) => todo.id === action.id); todo.done = !todo.done; break; case "add": draft.push({ id: action.id, title: "A new todo", done: false }); break; default: break; } }, [ /* initial todos */ ] ); //etc } ``` -------------------------------- ### Freezing: freeze(), setAutoFreeze() Source: https://immerjs.github.io/immer/api Utilities for controlling object freezing behavior. ```APIDOC ## Freezing Utilities ### Description These functions control how Immer handles freezing of objects. ### `freeze(obj: any, deep?: boolean): any` Freezes draftable objects. Returns the original object. By default freezes shallowly, but if the second argument is `true` it will freeze recursively. ### `setAutoFreeze(enable: boolean): void` Enables / disables automatic freezing of the trees produced. By default enabled. ``` -------------------------------- ### Array Methods: enableArrayMethods() Source: https://immerjs.github.io/immer/api Enables optimized array method handling for improved performance. ```APIDOC ## `enableArrayMethods()` ### Description Enables optimized array method handling for improved performance with array-heavy operations. ### Method `enableArrayMethods()` ### Usage Call this function once, typically at the start of your application, to enable the optimizations. ``` -------------------------------- ### Import produce from Immer Source: https://immerjs.github.io/immer/api In most cases, `produce` is the only function you need to import from Immer. In older versions, `produce` was also available as a default export, but this is no longer supported. ```javascript import {produce} from "immer" ``` -------------------------------- ### Standard Immer Producer Source: https://immerjs.github.io/immer/curried-produce Use this pattern when you need to apply a modification recipe to a specific state. It involves passing both the state and the recipe function to `produce`. ```javascript import {produce} from "immer" function toggleTodo(state, id) { return produce(state, draft => { const todo = draft.find(todo => todo.id === id) todo.done = !todo.done }) } const baseState = [ { id: "JavaScript", title: "Learn TypeScript", done: true }, { id: "Immer", title: "Try Immer", done: false } ] const nextState = toggleTodo(baseState, "Immer") ``` -------------------------------- ### Patch Support: enablePatches() Source: https://immerjs.github.io/immer/api Enables support for JSON patches. ```APIDOC ## `enablePatches()` ### Description Enables support for generating and applying JSON patches. ### Method `enablePatches()` ### Usage Call this function once, typically at the start of your application, to enable patch generation and application. ``` -------------------------------- ### Core API: produce Source: https://immerjs.github.io/immer/api The core API of Immer, used for creating immutable updates. ```APIDOC ## produce ### Description The core API of Immer. It takes the current state and a recipe function, and returns the next state. ### Method `produce` ### Parameters - **baseState** (any) - The initial state. - **recipe** (function) - A function that receives a draft of the state and applies mutations. ### Request Example ```javascript import { produce } from "immer" const baseState = { a: 1 }; const nextState = produce(baseState, draft => { draft.a = draft.a + 1; }); // nextState is { a: 2 } ``` ### Response #### Success Response (200) - **nextState** (any) - The new immutable state after applying mutations. ``` -------------------------------- ### Immer Instance: Immer Source: https://immerjs.github.io/immer/api Constructor to create a separate Immer instance. ```APIDOC ## `Immer` constructor ### Description Constructor that can be used to create a second "immer" instance (exposing all APIs listed in this instance), that doesn't share its settings with the global instance. ### Usage ```javascript import Immer from "immer" const immerInstance = new Immer(); const nextState = immerInstance.produce(baseState, draft => { // ... mutations }); ``` ``` -------------------------------- ### Return Value: nothing Source: https://immerjs.github.io/immer/api A special value to indicate that `undefined` should be produced. ```APIDOC ## `nothing` ### Description Value that can be returned from a recipe, to indicate that the value `undefined` should be produced. ### Usage ```javascript import { produce, nothing } from "immer" const baseState = { a: 1 }; const nextState = produce(baseState, draft => { draft.a = nothing; }); // nextState is {} ``` ``` -------------------------------- ### Update Immutable State Without Immer Source: https://immerjs.github.io/immer Demonstrates the manual process of updating immutable state by shallow copying and spreading objects. This approach requires careful handling at each level to avoid mutations. ```javascript const nextState = baseState.slice() // shallow clone the array nextState[1] = { // replace element 1... ...nextState[1], // with a shallow clone of element 1 done: true // ...combined with the desired update } // since nextState was freshly cloned, using push is safe here, // but doing the same thing at any arbitrary time in the future would // violate the immutability principles and introduce a bug! nextState.push({title: "Tweet about it"}) ``` -------------------------------- ### Curried Producer with Initial State Inference Source: https://immerjs.github.io/immer/typescript Shows how Immer can infer the state type for a curried producer when an initial state is provided, eliminating the need for explicit type annotations. ```typescript const state0: Todo = { title: "test", done: false } // No type annotations needed, since we can infer from state0. const toggler = produce(draft => { draft.done = !draft.done }, state0) // typeof toggler = (state: Todo) => Todo ``` -------------------------------- ### Immutable Updates with Class Instances Source: https://immerjs.github.io/immer/complex-objects Demonstrates how to use Immer's `produce` function with a class instance. The `tick` method returns a new instance of `Clock` with the minute incremented, preserving the class prototype. ```javascript import {immerable, produce} from "immer" class Clock { [immerable] = true constructor(hour, minute) { this.hour = hour this.minute = minute } get time() { return `${this.hour}:${this.minute}` } tick() { return produce(this, draft => { draft.minute++ }) } } const clock1 = new Clock(12, 10) const clock2 = clock1.tick() console.log(clock1.time) // 12:10 console.log(clock2.time) // 12:11 console.log(clock2 instanceof Clock) // true ``` -------------------------------- ### Producing undefined with Immer's `nothing` token Source: https://immerjs.github.io/immer/return Illustrates how to intentionally produce an `undefined` state using Immer's `nothing` token, differentiating it from a producer that doesn't return anything. ```javascript import {produce, nothing} from "immer" const state = { hello: "world" } produce(state, draft => {}) produce(state, draft => undefined) // Both return the original state: { hello: "world"} produce(state, draft => nothing) // Produces a new state, 'undefined' ``` -------------------------------- ### Draft Inspection: current, isDraft, original Source: https://immerjs.github.io/immer/api Utilities for inspecting draft objects. ```APIDOC ## Draft Inspection ### Description These functions help in inspecting the state of draft objects. ### `current(draft: Draft): any` Given a draft object (doesn't have to be a tree root), takes a snapshot of the current state of the draft. ### `isDraft(obj: any): boolean` Returns true if the given object is a draft object. ### `original(draft: Draft): any` Given a draft object (doesn't have to be a tree root), returns the original object at the same path in the original state tree, if present. ``` -------------------------------- ### Using Not-Overridden Array Methods Source: https://immerjs.github.io/immer/array-methods Illustrates how methods like `forEach` and `map`, which are not overridden by the Immer plugin, function. Callbacks for these methods receive draft values, and mutations within them are tracked normally. ```javascript const base = { items: [ {id: 1, value: 10, nested: {count: 0}}, {id: 2, value: 20, nested: {count: 0}} ] } const result = produce(base, draft => { // forEach receives drafts - mutations work normally draft.items.forEach(item => { item.value *= 2 }) // map is NOT overridden - callbacks receive drafts // The returned array items are also drafts (extracted from draft.items) const mapped = draft.items.map(item => item.nested) // Mutations to the result array propagate back mapped[0].count = 999 // ✅ This affects draft.items[0].nested.count }) console.log(result.items[0].nested.count) // 999 ``` -------------------------------- ### Verifying state changes with expect Source: https://immerjs.github.io/immer/produce Verify that the `baseState` remains unmodified and `nextState` contains the applied changes. Also checks for structural sharing of unchanged data. ```javascript // the new item is only added to the next state, // base state is unmodified expect(baseState.length).toBe(2) expect(nextState.length).toBe(3) // same for the changed 'done' prop expect(baseState[1].done).toBe(false) expect(nextState[1].done).toBe(true) // unchanged data is structurally shared expect(nextState[0]).toBe(baseState[0]) // ...but changed data isn't. expect(nextState[1]).not.toBe(baseState[1]) ``` -------------------------------- ### Drafting API: createDraft, finishDraft Source: https://immerjs.github.io/immer/api APIs for manually creating and finishing drafts, useful for asynchronous operations. ```APIDOC ## Manual Drafting ### Description These functions allow for manual control over the draft creation and finalization process, useful in asynchronous scenarios. ### `createDraft(state: any): Draft` Given a base state, creates a mutable draft for which any modifications will be recorded. ### `finishDraft(draft: Draft): any` Given an draft created using `createDraft`, seals the draft and produces and returns the next immutable state that captures all the changes. ``` -------------------------------- ### useImmer Hook for Simplified State Updates Source: https://immerjs.github.io/immer/example-setstate Leverage the `useImmer` hook from the `use-immer` package to automatically wrap state updater functions with Immer's `produce`. This reduces boilerplate code when working with `useState`. ```javascript import React, { useCallback } from "react"; import { useImmer } from "use-immer"; const TodoList = () => { const [todos, setTodos] = useImmer([ { id: "React", title: "Learn React", done: true }, { id: "Immer", title: "Try Immer", done: false } ]); const handleToggle = useCallback((id) => { setTodos((draft) => { const todo = draft.find((todo) => todo.id === id); todo.done = !todo.done; }); }, []); const handleAdd = useCallback(() => { setTodos((draft) => { draft.push({ id: "todo_" + Math.random(), title: "A new todo", done: false }); }); }, []); // etc } ``` -------------------------------- ### Inline mutations with `void` operator Source: https://immerjs.github.io/immer/return Shows how to use the JavaScript `void` operator for concise inline mutations within Immer producers, especially for single or multiple updates. ```javascript // Single mutation produce(draft => void (draft.user.age += 1)) ``` ```javascript // Multiple mutations produce(draft => void ((draft.user.age += 1), (draft.user.height = 186))) ``` -------------------------------- ### Update Immutable State With Immer Source: https://immerjs.github.io/immer Shows how to update immutable state using Immer's `produce` function. Mutations are applied to a `draft` object within a recipe function, simplifying the process and ensuring immutability. ```javascript import {produce} from "immer" const nextState = produce(baseState, draft => { draft[1].done = true draft.push({title: "Tweet about it"}) }) ``` -------------------------------- ### Patching API: applyPatches Source: https://immerjs.github.io/immer/api Applies a set of patches to a base state or draft. ```APIDOC ## applyPatches ### Description Given a base state or draft, and a set of patches, applies the patches. ### Method `applyPatches` ### Parameters - **state** (any) - The base state or draft to apply patches to. - **patches** (Array) - An array of patch objects to apply. ### Request Example ```javascript import { applyPatches } from "immer" const baseState = { a: 1 }; const patches = [{ op: "replace", path: ["a"], value: 2 }]; const nextState = applyPatches(baseState, patches); // nextState is { a: 2 } ``` ### Response #### Success Response (200) - **nextState** (any) - The new state after applying patches. ``` -------------------------------- ### Curried Producer with Explicit State Generic Source: https://immerjs.github.io/immer/typescript Demonstrates specifying the generic state type for a curried producer to skip automatic output widening and input narrowing, resulting in cleaner types. ```typescript const toggler = produce(draft => { draft.done = !draft.done }) // typeof toggler = (state: Todo) => Todo ``` -------------------------------- ### Update Maps with Immer Source: https://immerjs.github.io/immer/map-set Demonstrates how to modify Map objects within an Immer producer. Each modification results in a new Map instance. Attempts to mutate the Map outside a producer will throw an error. ```javascript test("Producers can update Maps", () => { const usersById_v1 = new Map() const usersById_v2 = produce(usersById_v1, draft => { // Modifying a map results in a new map draft.set("michel", {name: "Michel Weststrate", country: "NL"}) }) const usersById_v3 = produce(usersById_v2, draft => { // Making a change deep inside a map, results in a new map as well! draft.get("michel").country = "UK" }) // We got a new map each time! expect(usersById_v2).not.toBe(usersById_v1) expect(usersById_v3).not.toBe(usersById_v2) // With different content obviously expect(usersById_v1).toMatchInlineSnapshot(`Map {}`) expect(usersById_v2).toMatchInlineSnapshot(` Map { "michel" => Object { "country": "NL", "name": "Michel Weststrate", }, } `) expect(usersById_v3).toMatchInlineSnapshot(` Map { "michel" => Object { "country": "UK", "name": "Michel Weststrate", }, } `) // The old one was never modified expect(usersById_v1.size).toBe(0) // And trying to change a Map outside a producers is going to: NO! expect(() => usersById_v3.clear()).toThrowErrorMatchingInlineSnapshot( `"This object has been frozen and should not be mutated"` ) }) ``` -------------------------------- ### Enable Array Methods Plugin in Immer Source: https://immerjs.github.io/immer/performance Enable the Array Methods Plugin to optimize array operations like filter, find, some, every, and slice by avoiding proxy creation for every element during iteration. This is beneficial for applications with significant array iteration within producers. ```javascript import {enableArrayMethods} from "immer" enableArrayMethods() ``` -------------------------------- ### Object Mutations with Immer Source: https://immerjs.github.io/immer/update-patterns Use Immer's produce function to immutably update properties of an object. Immer handles returning new references when mutations occur. ```javascript import {produce} from "immer" const todosObj = { id1: {done: false, body: "Take out the trash"}, id2: {done: false, body: "Check Email"} } // add const addedTodosObj = produce(todosObj, draft => { draft["id3"] = {done: false, body: "Buy bananas"} }) // delete single property const deletedTodosObj = produce(todosObj, draft => { delete draft["id1"] }) // update const updatedTodosObj = produce(todosObj, draft => { draft["id1"].done = true }) // replace & update in bulk const updatedTodosObj = produce(todosObj, draft => { Object.assign(draft, { id1: {done: true, body: "Take out the trash"}, id2: {done: true, body: "Check Email"}, id3: {done: true, body: "Feed my cat"} }) // reset/clear/empty const emptyTodo = produce(todosObj, () => { return {}; }) ``` -------------------------------- ### Use `original` for Referential Equality Checks in Immer Source: https://immerjs.github.io/immer/pitfalls When comparing an original object with its draft or needing to access original properties within a producer, use the `original` helper function. Direct comparison using `===` or `==` will fail due to Immer's proxy wrapping. ```javascript const remove = produce((list, element) => { const index = list.indexOf(element) // this won't work! const index = original(list).indexOf(element) // do this instead if (index > -1) list.splice(index, 1) }) const values = [a, b, c] remove(values, a) ``` -------------------------------- ### Set Immer to Use Loose Iteration Source: https://immerjs.github.io/immer/performance Configure Immer to use loose iteration for better performance by only processing enumerable string properties. This is the default behavior and is optimal for most use cases. Enable strict iteration only if you need to track symbol or non-enumerable properties. ```javascript import {setUseStrictIteration} from "immer" // Default: false (loose iteration for better performance) setUseStrictIteration(false) ``` -------------------------------- ### Curried Producer with React useState Source: https://immerjs.github.io/immer/typescript Shows how to use a curried Immer producer with React's `useState` hook. Immer infers types when the producer is passed directly to `setTodo`. ```typescript import {Immutable, produce} from "immer" type Todo = Immutable<{ title: string done: boolean }> // later... const [todo, setTodo] = useState({ title: "test", done: true }) // later... setTodo( produce(draft => { // draft will be strongly typed and mutable! draft.done = !draft.done }) ) ``` -------------------------------- ### Immer Subset Operations: Filter, Find, Slice Source: https://immerjs.github.io/immer/array-methods Subset operations like filter, find, and slice return drafts. Mutations to these returned items WILL affect the draft state. Callbacks receive base values for optimization. ```javascript const base = { items: [ {id: 1, value: 10}, {id: 2, value: 20}, {id: 3, value: 30} ] } const result = produce(base, draft => { // filter returns drafts - mutations track back to original const filtered = draft.items.filter(item => item.value > 15) filtered[0].value = 999 // This WILL affect draft.items[1] // find returns a draft - mutations track back const found = draft.items.find(item => item.id === 3) if (found) { found.value = 888 // This WILL affect draft.items[2] } // slice returns drafts const sliced = draft.items.slice(0, 2) sliced[0].value = 777 // This WILL affect draft.items[0] }) console.log(result.items[0].value) // 777 console.log(result.items[1].value) // 999 console.log(result.items[2].value) // 888 ``` -------------------------------- ### TypeScript Utilities: castDraft, castImmutable, Draft, Immutable Source: https://immerjs.github.io/immer/api Utilities for working with Immer's draftable types in TypeScript. ```APIDOC ## TypeScript Utilities ### Description These utilities are for TypeScript users to help manage types when working with Immer. ### `castDraft(obj: T): Draft` Converts any immutable type to its mutable counterpart. This is just a cast and doesn't actually do anything. ### `castImmutable(obj: T): Immutable` Converts any mutable type to its immutable counterpart. This is just a cast and doesn't actually do anything. ### `Draft` Exposed TypeScript type to convert an immutable type to a mutable type. ### `Immutable` Exposed TypeScript type to convert mutable types to immutable types. ``` -------------------------------- ### Strict Copying: setUseStrictShallowCopy() Source: https://immerjs.github.io/immer/api Enables strict shallow copy behavior. ```APIDOC ## `setUseStrictShallowCopy(enable: boolean)` ### Description Can be used to enable strict shallow copy. If enabled, Immer copies non-enumerable properties as much as possible. ### Method `setUseStrictShallowCopy` ### Usage Call this function to control the strictness of shallow copies. ``` -------------------------------- ### useReducer + Immer for Reducer State Management Source: https://immerjs.github.io/immer/example-setstate Integrate Immer with React's `useReducer` hook to simplify state transitions within reducers. This approach is beneficial for managing more complex state logic. ```javascript import React, {useCallback, useReducer} from "react" import {produce} from "immer" const TodoList = () => { const [todos, dispatch] = useReducer( produce((draft, action) => { switch (action.type) { case "toggle": const todo = draft.find(todo => todo.id === action.id) todo.done = !todo.done break case "add": draft.push({ id: action.id, title: "A new todo", done: false }) break default: break } }), [ /* initial todos */ ] ) const handleToggle = useCallback(id => { dispatch({ type: "toggle", id }) }, []) const handleAdd = useCallback(() => { dispatch({ type: "add", id: "todo_" + Math.random() }) }, []) // etc } ``` -------------------------------- ### Curried Producer with Explicit Draft Type Source: https://immerjs.github.io/immer/typescript Illustrates defining a curried producer where the draft type is explicitly specified using `Draft`. This is necessary for readonly types. ```typescript // See below for a better solution! const toggler = produce((draft: Draft) => { draft.done = !draft.done }) // typeof toggler = (state: Immutable) => Writable ``` -------------------------------- ### Callback Behavior with Base Values Source: https://immerjs.github.io/immer/array-methods Shows that for overridden methods, callbacks receive base values, not drafts. This is an optimization to avoid proxy creation for every element during iteration. Direct mutations to these base values are not tracked. ```javascript const base = { items: [ {id: 1, value: 10}, {id: 2, value: 20} ] } produce(base, draft => { draft.items.filter(item => { // `item` is a base value here, NOT a draft // Reading properties works fine return item.value > 15 // But direct mutation here won't be tracked: // item.value = 999 // ❌ Won't affect draft }) // Instead, use the returned draft: const filtered = draft.items.filter(item => item.value > 15) filtered[0].value = 999 // ✅ This works because filtered[0] is a draft }) ``` -------------------------------- ### Draftability Check: isDraftable Source: https://immerjs.github.io/immer/api Checks if Immer can create a draft from an object. ```APIDOC ## `isDraftable(obj: any): boolean` ### Description Returns true if Immer is capable of turning this object into a draft. Which is true for: arrays, objects without prototype, objects with `Object` as their prototype, objects that have the `immerable` symbol on their constructor or prototype. ### Method `isDraftable` ``` -------------------------------- ### Curried Immer Producer Source: https://immerjs.github.io/immer/curried-produce This curried form of `produce` is useful for creating reusable modification functions. It returns a new function that accepts the state and any additional arguments needed by the recipe. ```javascript import {produce} from "immer" // curried producer: const toggleTodo = produce((draft, id) => { const todo = draft.find(todo => todo.id === id) todo.done = !todo.done }) const baseState = [ /* as is */ ] const nextState = toggleTodo(baseState, "Immer") ``` -------------------------------- ### Class Integration: immerable Source: https://immerjs.github.io/immer/api Symbol to indicate that a class should be treated as draftable by Immer. ```APIDOC ## `immerable` Symbol ### Description Symbol that can be added to a constructor or prototype, to indicate that Immer should treat the class as something that can be safely drafted. ### Usage ```javascript class MyClass { // ... } MyClass[immerable] = true; const instance = new MyClass(); const nextState = produce(instance, draft => { // ... mutations on draft }); ``` ``` -------------------------------- ### useState + Immer for Deep Updates Source: https://immerjs.github.io/immer/example-setstate Use Immer's `produce` with `useState` to simplify deep state updates in React components. This is useful when dealing with nested state objects or arrays. ```javascript import React, { useCallback, useState } from "react"; import {produce} from "immer"; const TodoList = () => { const [todos, setTodos] = useState([ { id: "React", title: "Learn React", done: true }, { id: "Immer", title: "Try Immer", done: false } ]); const handleToggle = useCallback((id) => { setTodos( produce((draft) => { const todo = draft.find((todo) => todo.id === id); todo.done = !todo.done; }) ); }, []); const handleAdd = useCallback(() => { setTodos( produce((draft) => { draft.push({ id: "todo_" + Math.random(), title: "A new todo", done: false }); }) ); }, []); return (
{*/ See CodeSandbox */}
) } ``` -------------------------------- ### Immer Import Size Report Source: https://immerjs.github.io/immer/installation This report, generated by npmjs.com/package/import-size, breaks down the import size of Immer and its features. It helps in understanding the bundle size impact of enabling specific functionalities. ```plaintext Import size report for immer: ┌───────────────────────┬───────────┬────────────┬───────────┐ │ (index) │ just this │ cumulative │ increment │ ├───────────────────────┼───────────┼────────────┼───────────┤ │ import * from 'immer' │ 6908 │ 0 │ 0 │ │ produce │ 4183 │ 4183 │ 0 │ │ enableMapSet │ 4971 │ 4980 │ 797 │ │ enablePatches │ 5335 │ 6097 │ 1117 │ │ enableArrayMethods │ 4768 │ 6659 │ 562 │ └───────────────────────┴───────────┴────────────┴───────────┘ (this report was generated by npmjs.com/package/import-size) ``` -------------------------------- ### Immer Array Methods Plugin: Filter Callback Receives Base Values Source: https://immerjs.github.io/immer/pitfalls When using enableArrayMethods(), callbacks for overridden methods like filter receive base values, not drafts. Direct mutation within these callbacks is not tracked. To mutate, use the returned result which contains drafts. ```javascript import {enableArrayMethods, produce} from "immer" enableArrayMethods() produce(state, draft => { draft.items.filter(item => { // `item` is a base value here, NOT a draft // Reading works fine: return item.value > 10 // But direct mutation here won't be tracked: // item.value = 999 // ❌ Won't affect the draft! }) // Instead, use the returned result (which contains drafts): const filtered = draft.items.filter(item => item.value > 10) filtered[0].value = 999 // ✅ This works - filtered[0] is a draft }) ```