immer Documentation

Create the next immutable state by mutating the current one

28,987 stars JavaScript JavaScript 26 files ยท ~22,984 tokens #immutable#immutables#reducer#redux#state-tree
RAW Doc

Technical Documentation: immerjs/immer

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`readme.md`)

Immer

Create the next immutable state tree by simply modifying the current tree

Winner of the "Breakthrough of the year" React open source award and "Most impactful contribution" JavaScript open source award in 2019

Contribute using one-click online setup

You can use Gitpod (a free online VSCode like IDE) for contributing online. With a single click it will launch a workspace and automatically:

  • clone the immer repo.
  • install the dependencies.
  • run yarn run start.

so that you can start coding straight away.

Documentation

The documentation of this package is hosted at https://immerjs.github.io/immer/

Support

Did Immer make a difference to your project? Join the open collective at https://opencollective.com/immer!

Release notes

https://github.com/immerjs/immer/releases


Overview (`perf-testing/README.md`)

Immer Performance Testing

This directory contains performance testing tools for Immer, allowing you to benchmark different versions of Immer and analyze CPU profiles to identify performance bottlenecks.

Setup

  1. Install dependencies (in this directory):

    bash
    yarn install
  2. Build Immer first:

    bash
    yarn build-immer
  3. Build the benchmark bundle:

    bash
    yarn build

Alternately, you can rebuild both Immer and the benchmarking script:

bash
yarn build-with-latest

Usage

Running Benchmarks

To run the performance benchmarks:

bash
# Run basic benchmarks, with relative version speed comparisons
yarn benchmark

# Run the benchmarks, but also generate a CPU profile
yarn profile

Analyzing CPU Profiles

After running yarn profile, you'll get a .cpuprofile file. To analyze it:

bash
# Analyze the most recent profile
yarn analyze-profile your-profile.cpuprofile

What's Included

  • immutability-benchmarks.mjs: Main benchmark script comparing different Immer versions
  • read-cpuprofile.js: Advanced CPU profile analyzer with sourcemap support
  • rolldown.config.js: Bundler configuration that eliminates process.env overhead

Benchmark Versions

The benchmarks compare:

  • immer7-10: Historical Immer versions
  • immer10Perf: Current development version (references ../dist)
  • vanilla: Pure JavaScript implementations for baseline comparison

Key Features

  • Sourcemap support: CPU profile analysis includes original function names
  • Version-aware analysis: Breaks down performance by Immer version
  • Production bundling: Uses Rolldown to eliminate development overhead

Api (`website/docs/api.md`)


id: api title: API overview

Exported name Description Section
produce The core API of Immer: import {produce} from "immer" Produce
applyPatches Given a base state or draft, and a set of patches, applies the patches Patches
castDraft Converts any immutable type to its mutable counterpart. This is just a cast and doesn't actually do anything. TypeScript
castImmutable Converts any mutable type to its immutable counterpart. This is just a cast and doesn't actually do anything. TypeScript
createDraft Given a base state, creates a mutable draft for which any modifications will be recorded Async
current Given a draft object (doesn't have to be a tree root), takes a snapshot of the current state of the draft Current
Draft<T> Exposed TypeScript type to convert an immutable type to a mutable type TypeScript
enableArrayMethods() Enables optimized array method handling for improved performance with array-heavy operations. Array Methods
enableMapSet() Enables support for Map and Set collections. Installation
enablePatches() Enables support for JSON patches. Installation
finishDraft Given an draft created using createDraft, seals the draft and produces and returns the next immutable state that captures all the changes Async
freeze(obj, deep?) Freezes draftable objects. Returns the original object. By default freezes shallowly, but if the second argument is true it will freeze recursively.
Immer 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 global instance.
immerable 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 Classes
Immutable<T> Exposed TypeScript type to convert mutable types to immutable types
isDraft Returns true if the given object is a draft object
isDraftable 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
nothing Value that can be returned from a recipe, to indicate that the value undefined should be produced Return
original 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 Original
Patch Exposed TypeScript type, describes the shape of an (inverse) patch object Patches
produceWithPatches Works the same as produce, but instead of just returning the produced object, it returns a tuple, consisting of [result, patches, inversePatches]. Patches
setAutoFreeze Enables / disables automatic freezing of the trees produces. By default enabled. Freezing
setUseStrictShallowCopy Can be used to enable strict shallow copy. If enable, immer copies non-enumerable properties as much as possible. Classes
setUseStrictIteration Controls iteration behavior: pass false for loose iteration (enumerable properties only, better performance) or true for strict iteration (includes symbols and non-enumerable properties). By default disabled (loose iteration).

Importing immer

In most cases, the only thing you need to import from Immer is produce:

javascript
import {produce} from "immer"

Note that in older versions, produce was also available as default export (e.g. import produce from "immer" was also valid, but that is no longer the case to improve eco system compatibility.


Array Methods (`website/docs/array-methods.md`)


id: array-methods title: Array Methods Plugin

Overview

The Array Methods Plugin (enableArrayMethods()) optimizes array operations within Immer producers by avoiding unnecessary Proxy creation during iteration. This provides significant performance improvements for array-heavy operations.

Why does this matter? Without the plugin, every array element access during iteration (e.g., in filter, find, slice) creates a Proxy object. For a 1000-element array, this means 1000+ proxy trap invocations just to iterate. With the plugin enabled, callbacks receive base (non-proxied) values, and proxies are only created as needed for mutation tracking.

Installation

Enable the plugin once at your application's entry point:

javascript
import {enableArrayMethods} from "immer"

enableArrayMethods()

This adds approximately 2KB to your bundle size.

Mutating Methods

These methods modify the array in-place and operate directly on the draft's internal copy without creating per-element proxies:

Method Returns Description
push() New length Adds elements to the end
pop() Removed element Removes and returns the last element
shift() Removed element Removes and returns the first element
unshift() New length Adds elements to the beginning
splice() Removed elements Adds/removes elements at any position
sort() The draft array Sorts elements in place
reverse() The draft array Reverses the array in place
javascript
import {produce, enableArrayMethods} from "immer"

enableArrayMethods()

const base = {items: [3, 1, 4, 1, 5]}

const result = produce(base, draft => {
	draft.items.push(9) // Adds 9 to end
	draft.items.sort() // Sorts: [1, 1, 3, 4, 5, 9]
	draft.items.reverse() // Reverses: [9, 5, 4, 3, 1, 1]
})

Non-Mutating Methods

Non-mutating methods are categorized based on what they return:

Subset Operations (Return Drafts)

These methods select items that exist in the original array and create draft proxies for the returned items. The callbacks receive base values (the optimization), but the returned array contains newly created draft proxies that point back to the original positions. Mutations to returned items WILL affect the draft state.

Method Returns Drafts?
filter() Array of matching items โœ… Yes
slice() Array of items in range โœ… Yes
find() First matching item or undefined โœ… Yes
findLast() Last matching item or undefined โœ… Yes
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

Transform Operations (Return Base Values)

These methods create new arrays that may include external items or restructured data. They return base values, NOT drafts. Mutations to returned items will NOT track back to the draft state.

Method Returns Drafts?
concat() New combined array โŒ No
flat() New flattened array โŒ No
javascript
const base = {items: [{id: 1, value: 10}]}

const result = produce(base, draft => {
	// concat returns base values - mutations DON'T track
	const concatenated = draft.items.concat([{id: 2, value: 20}])
	concatenated[0].value = 999 // This will NOT affect draft.items[0]

	// To actually use concat results, assign them:
	draft.items = draft.items.concat([{id: 2, value: 20}])
})

// Original unchanged because concat result wasn't assigned
console.log(result.items[0].value) // 10 (unchanged)

Why the distinction?

  • Subset operations (filter, slice, find) select items that exist in the original array. Returning drafts allows mutations to propagate back to the source.
  • Transform operations (concat, flat) create new data structures that may include external items or restructured data, making draft tracking impractical.

Primitive-Returning Methods

These methods return primitive values (numbers, booleans, strings). No tracking issues since primitives aren't draftable:

Method Returns
indexOf() Number (index or -1)
lastIndexOf() Number (index or -1)
includes() Boolean
some() Boolean
every() Boolean
findIndex() Number (index or -1)
findLastIndex() Number (index or -1)
join() String
toString() String
toLocaleString() String
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
})

Methods NOT Overridden

The following methods are not intercepted by the plugin and work through standard Proxy behavior. Callbacks receive drafts, and mutations track normally:

Method Description
map() Transform each element
flatMap() Map then flatten
forEach() Execute callback for each element
reduce() Reduce to single value
reduceRight() Reduce from right to left
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

Callback Behavior

For overridden methods, callbacks receive base values (not drafts). This is the core optimization - it avoids creating proxies for every element during iteration.

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
})

Method Return Behavior Summary

Category Methods Returns Mutations Track?
Subset filter, slice, find, findLast Draft proxies โœ… Yes
Transform concat, flat Base values โŒ No
Primitive indexOf, includes, some, every, findIndex, findLastIndex, lastIndexOf, join, toString, toLocaleString Primitives N/A
Mutating push, pop, shift, unshift, splice, sort, reverse Various โœ… Yes (modifies draft)
Not Overridden map, flatMap, forEach, reduce, reduceRight Standard behavior โœ… Yes (callbacks get drafts)

When to Use

Enable the Array Methods Plugin when:

  • Your application has significant array iteration within producers
  • You frequently use methods like filter, find, some, every on large arrays
  • Performance profiling shows array operations as a bottleneck

The plugin is most beneficial for:

  • Large arrays (100+ elements)
  • Frequent producer calls with array operations
  • Read-heavy operations (filtering, searching) where most elements aren't modified

Performance Benefit

Without the plugin:

  • Every array element access during iteration creates a Proxy
  • A filter() on 1000 elements = 1000+ proxy creations

With the plugin:

  • Callbacks receive base values directly
  • Proxies only created for the specific elements you actually mutate, or that 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
	})
})

Async (`website/docs/async.mdx`)


id: async title: createDraft / finishDraft sidebar_label: createDraft / finishDraft

text
egghead.io lesson 11: Creating **async** producers (and why you
	shouldnโ€™t)



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-write-asynchronous-producers-in-immer-and-why-you-shouldn-t"
>
	Hosted on egghead.io
</a>

`createDraft` and `finishDraft`

createDraft and finishDraft are two low-level functions that are mostly useful for libraries that build abstractions on top of immer. It avoids the need to always create a function in order to work with drafts. Instead, one can create a draft, modify it, and at some time in the future finish the draft, in which case the next immutable state will be produced.

Beyond that, createDraft / finishDraft could be used to express async updates to drafts:

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)

Note: The above is an anti-pattern! First fetch data instead, then draft the user. Otherwise updates to user that happen during the async process, would be "missed" by the draft.

Note: finishDraft takes a patchListener as second argument, which can be used to record the patches, similarly to produce.

Warning: in general, we recommend to use produce instead of the createDraft / finishDraft combo, produce is less error prone in usage, and more clearly separates the concepts of mutability and immutability in your code base.


Built With (`website/docs/built-with.md`)


id: built-with title: Built with Immer

  • react-copy-write Immutable state with a mutable API
  • redux-toolkit The official, opinionated, batteries-included toolset for efficient Redux development
  • immer based handleActions Boilerplate free actions for Redux
  • redux-box Modular and easy-to-grasp redux based state management, with least boilerplate
  • quick-redux tools to make redux development quicker and easier
  • bey Simple immutable state for React using Immer
  • cool-store CoolStore is an immutable state store built on top of ImmerJS and RxJS
  • immer-wieder State management lib that combines React 16 Context and immer for Redux semantics
  • robodux flexible way to reduce redux boilerplate
  • immer-reducer Type-safe and terse React (useReducer()) and Redux reducers with Typescript
  • redux-ts-utils Everything you need to create type-safe applications with Redux with a strong emphasis on simplicity
  • react-state-tree Drop-in replacement for useState that persists your state into a redux-like state tree
  • redux-immer is used to create an equivalent function of Redux combineReducers that works with immer state. Like redux-immutable but for immer
  • ngrx-wieder Lightweight yet configurable solution for implementing undo-redo in Angular apps on top of NgRx and Immer
  • immer-yjs Combine immer with CRDT library y.js for easy json data manipulation
  • immerhin Sync state with undo/redo
  • zenith Reactive React state management on Immer with MobX-style computed properties and patch-based undo/redo, powering DOMD.
  • domd High-performance macOS markdown editor built on Immer's structural sharing, with near-O(1) per-keystroke rendering on large documents.
  • ... and many more

Complex Objects (`website/docs/complex-objects.md`)


id: complex-objects title: Classes

Plain objects (objects without a prototype), arrays, Maps and Sets are always drafted by Immer. Every other object must use the immerable symbol to mark itself as compatible with Immer. When one of these objects is mutated within a producer, its prototype is preserved between copies.

js
import {immerable} from "immer"

class Foo {
	[immerable] = true // Option 1

	constructor() {
		this[immerable] = true // Option 2
	}
}

Foo[immerable] = true // Option 3

Example

js
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

Semantics in detail

The semantics on how classes are drafted are as follows:

  1. A draft of a class is a fresh object but with the same prototype as the original object.
  2. When creating a draft, Immer will copy all own properties from the base to the draft.This includes (in strict mode) non-enumerable and symbolic properties.
  3. Own getters will be invoked during the copy process, just like Object.assign would.
  4. Inherited getters and methods will remain as is and be inherited by the draft, as they are stored on the prototype which is untouched.
  5. Immer will not invoke constructor functions.
  6. The final instance will be constructed with the same mechanism as the draft was created.
  7. Only getters that have a setter as well will be writable in the draft, as otherwise the value can't be copied back.

Because Immer will dereference own getters of objects into normal properties, it is possible to use objects that use getter/setter traps on their fields, like MobX and Vue do.

Note that, by default, Immer does not strictly handle object's non-enumerable properties such as getters/setters for performance reason. If you want this behavior to be strict, you can opt-in with useStrictShallowCopy(config). Use true to always copy strict, or "class_only" to only copy class instances strictly but use the faster loose copying for plain objects. The default is false. (Remember, regardless of strict mode, own getters / setters are always copied by value. There is currently no config to copy descriptors as-is. Feature request / PR welcome).

Immer does not support exotic / engine native objects such as DOM Nodes or Buffers, nor is subclassing Map, Set or arrays supported and the immerable symbol can't be used on them.

So when working for example with Date objects, you should always create a new Date instance instead of mutating an existing Date object.


Current (`website/docs/current.md`)


id: current title: Extracting the current state from a draft sidebar_label: Current

Immer exposes a named export current that creates a copy of the current state of the draft. This can be very useful for debugging purposes (as those objects won't be Proxy objects and not be logged as such). Also, references to current can be safely leaked from a produce function. Put differently, current provides a snapshot of the current state of a draft.

Objects generated by current work similar to the objects created by produce itself.

  1. Unmodified objects will be structurally shared with the original objects.
  2. If no changes are made to a draft, generally it holds that original(draft) === current(draft), but this is not guaranteed.
  3. Future changes to the draft won't be reflected in the object produced by current (except for references to undraftable objects)
  4. Unlike produce objects created by current will not be frozen.

Use current sparingly, it can be a potentially expensive operation, especially when using ES5.

Note that current cannot be invoked on objects that aren't drafts.

Example

The following example shows the effect of current (and original):

js
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)

Curried Produce (`website/docs/curried-produce.mdx`)


id: curried-produce title: Curried producers

text
egghead.io lesson 6: Simplify code by using curried _reduce_



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/javascript-simplify-immer-producer-functions-using-currying"
>
	Hosted on egghead.io
</a>

Passing a function as the first argument to produce creates a function that doesn't apply produce yet to a specific state, but rather creates a function that will apply produce to any state that is passed to it in the future. This generally is called currying. Take for example the following example:

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")

The above pattern of toggleTodo is quite typical; pass an existing state to produce, modify the draft, and then return the result. Since state isn't used for anything else than passing it on to produce, the above example can be simplified by using the curried form of produce, where you pass produce only the recipe function, and produce will return a new function that will apply recipe to the base state. This allows us to shorten the above toggleTodo definition.

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")

Note that the id param has now become part of the recipe function! This pattern of having curried producers combines really neatly with for example the useState hook from React, as we will see on the next page.


Example Setstate (`website/docs/example-setstate.mdx`)


id: example-setstate title: React & Immer

text
egghead.io lesson 8: Using Immer with _useState_. Or: _useImmer_



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-immutable-update-state-inside-react-components-with-useimmer"
>
	Hosted on egghead.io
</a>

useState + Immer

The useState hook assumes any state that is stored inside it is treated as immutable. Deep updates in the state of React components can be greatly simplified as by using Immer. The following example shows how to use produce in combination with useState, and can be tried on CodeSandbox.

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 <div>{/* See CodeSandbox */}</div>
}

useImmer

Since all state updaters follow the same pattern where the update function is wrapped in produce, it is also possible to simplify the above by leveraging the use-immer package that will wrap updater functions in produce automatically:

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
      });
    });
  }, []);

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          <label>
            <input
              type="checkbox"
              checked={todo.done}
              onChange={() => handleToggle(todo.id)}
            />
            {todo.title}
          </label>
        </li>
      ))}
      <button type="button" onClick={handleAdd}>
        Add todo
      </button>
    </ul>
  );
};

useReducer + Immer

Similarly to useState, useReducer combines neatly with Immer as well, as demonstrated in this CodeSandbox:

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
}

useImmerReducer

...which again, can be slightly shorted by useImmerReducer from the use-immer package (demo):

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

Redux + Immer

Redux + Immer is extensively covered in the documentation of Redux Toolkit. For Redux without Redux Toolkit, the same trick as applied to useReducer above can be applied: wrap the reducer function with produce, and you can safely mutate the draft!

For example:

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
	}
})

Faq (`website/docs/faq.md`)


id: faq title: Frequently Asked Questions sidebar_label: FAQ

Q: How does Immer work?

Read the (second part of the) introduction blog.

Q: Does Immer use structural sharing? So that my selectors can be memoized and such?

A: Yes

Q: Does Immer support deep updates?

A: Yes

Q: I can't rely on Proxies being present on my target environments. Can I use Immer?

A: Yes - view details

Q: Can I typecheck my data structures when using Immer?

A: Yes

Q: Can I store `Date` objects, functions etc in my state tree when using Immer?

A: Yes

Q: Can I use Maps and Sets?

A: Yes

Q: Is it fast?

A: Yes

Q: Idea! Can Immer freeze the state for me?

A: Yes


Freezing (`website/docs/freezing.mdx`)


id: freezing title: Auto freezing

text
egghead.io lesson 7: Immer automatically freezes data



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/javascript-produces-immutable-data-and-avoid-unnecessary-creation-of-new-data-trees-with-immer"
>
	Hosted on egghead.io
</a>

Immer automatically freezes any state trees that are modified using produce. This protects against accidental modifications of the state tree outside of a producer. In most cases this provides the most optimal behavior, but setAutoFreeze(true / false) can be used to explicitly turn this feature on or off.

Immer will never freeze (the contents of) non-enumerable, non-own or symbolic properties, unless their content was drafted.

โš ๏ธ Immer freezes everything recursively, for large data objects that won't be changed in the future this might be over-kill, in that case it can be more efficient to shallowly pre-freeze data using the freeze utility.โš ๏ธ

โš ๏ธ If auto freezing is enabled, recipes are not entirely side-effect free: Any plain object or array that ends up in the produced result, will be frozen, even when these objects were not frozen before the start of the producer! โš ๏ธ


Installation (`website/docs/installation.mdx`)


id: installation title: Installation

Immer can be installed as a direct dependency, and will work in any ES5 environment:

  • Yarn: yarn add immer
  • NPM: npm install immer
  • Deno: deno install immer
  • CDN: Exposed global is immer
    • Unpkg: <script src="https://unpkg.com/immer"></script>
    • JSDelivr: <script src="https://cdn.jsdelivr.net/npm/immer"></script>
    • โš ๏ธ When using a CDN, it is best to check the url in your browser and see what version it resolves to, so that your users aren't accidentally served a newer version in the future when updates are release. So use a url like: https://unpkg.com/[email protected]/dist/immer.umd.production.min.js instead. Substitute production.min with development in the URL for a development build.

Pick your Immer version

This section only applies to version 6 and later

To make sure Immer is as small as possible, features that are not required by every project has been made opt-in, and have to be enabled explicitly. This ensures that when bundling your application for production, unused features don't take any space.

The following features can be opt-in to:

Feature Description Method to call
Array Methods optimization Optimizes array method handling for improved performance with array-heavy operations enableArrayMethods()
ES2015 Map and Set support To enable Immer to operate on the native Map and Set collections, enable this feature enableMapSet()
JSON Patch support Immer can keep track of all the changes you make to draft objects. This can be useful for communicating changes using JSON patches enablePatches()

For example, if you want to use produce on a Map, you need to enable this feature once during the start of your application:

typescript
// In your application's entrypoint
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")

Vanilla Immer kicks in at ~3KB gzipped. Every plugin that is enabled adds ~1-2 KB to that. The breakdown is as follows:

text
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 on older JavaScript environments?

By default produce tries to use proxies for optimal performance. However, on older JavaScript engines Proxy is not available. For example, when running Microsoft Internet Explorer or React Native (if < v0.59 or when using the Hermes engine on React Native < 0.64) on Android. In such cases, Immer will fallback to an ES5 compatible implementation which works identically, but is a bit slower.

  • Since version 6, support for the fallback implementation has to be explicitly enabled by calling enableES5().
  • Version 10 drops the fallback implementation fully, and cannot be used in browsers / engines that don't support Proxy.

Introduction (`website/docs/introduction.md`)


id: introduction title: Introduction to Immer sidebar_label: Introduction slug: /

Immer

Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way.

Immer is life-changing as a JS dev, and I'm not even exaggerating :) Like, it's right up there with Prettier in terms of "wow this package is amazing, how did I ever live without it?" --Mark Erikson, (the) Redux Maintainer, @replayio

Winner of the "Breakthrough of the year" React open source award and "Most impactful contribution" JavaScript open source award in 2019.



Immer simplifies handling immutable data structures

Immer can be used in any context in which immutable data structures need to be used. For example in combination with React state, React or Redux reducers, or configuration management. Immutable data structures allow for (efficient) change detection: if the reference to an object didn't change, the object itself did not change. In addition, it makes cloning relatively cheap: Unchanged parts of a data tree don't need to be copied and are shared in memory with older versions of the same state.

Generally speaking, these benefits can be achieved by making sure you never change any property of an object, array or map, but by always creating an altered copy instead. In practice this can result in code that is quite cumbersome to write, and it is easy to accidentally violate those constraints. Immer will help you to follow the immutable data paradigm by addressing these pain points:

  1. Immer will detect accidental mutations and throw an error.
  2. Immer will remove the need for the typical boilerplate code that is needed when creating deep updates to immutable objects: Without Immer, object copies need to be made by hand at every level. Typically by using a lot of ... spread operations. When using Immer, changes are made to a draft object, that records the changes and takes care of creating the necessary copies, without ever affecting the original object.
  3. When using Immer, you don't need to learn dedicated APIs or data structures to benefit from the paradigm. With Immer you'll use plain JavaScript data structures, and use the well-known mutable JavaScript APIs, but safely.

A quick example for comparison

javascript
const baseState = [
	{
		title: "Learn TypeScript",
		done: true
	},
	{
		title: "Try Immer",
		done: false
	}
]

Imagine we have the above base state, and we'll need to update the second todo, and add a third one. However, we don't want to mutate the original baseState, and we want to avoid deep cloning as well (to preserve the first todo).

Without Immer

Without Immer, we'll have to carefully shallow copy every level of the state structure that is affected by our change:

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"})

With Immer

With Immer, this process is more straightforward. We can leverage the produce function, which takes as first argument the state we want to start from, and as second argument we pass a function, called the recipe, that is passed a draft to which we can apply straightforward mutations. Those mutations are recorded and used to produce the next state once the recipe is done. produce will take care of all the necessary copying, and protect against future accidental modifications as well by freezing the data.

javascript
import {produce} from "immer"

const nextState = produce(baseState, draft => {
	draft[1].done = true
	draft.push({title: "Tweet about it"})
})

Looking for Immer in combination with React? Feel free to skip ahead to the React + Immer page.

How Immer works

The basic idea is that with Immer you will apply all your changes to a temporary draft, which is a proxy of the currentState. Once all your mutations are completed, Immer will produce the nextState based on the mutations to the draft state. This means that you can interact with your data by simply modifying it while keeping all the benefits of immutable data.

Using Immer is like having a personal assistant. The assistant takes a letter (the current state) and gives you a copy (draft) to jot changes onto. Once you are done, the assistant will take your draft and produce the real immutable, final letter for you (the next state).

Head to the next section to further dive into produce.

Benefits

  • Follow the immutable data paradigm, while using normal JavaScript objects, arrays, Sets and Maps. No new APIs or "mutation patterns" to learn!
  • Strongly typed, no string based paths selectors etc.
  • Structural sharing out of the box
  • Object freezing out of the box
  • Deep updates are a breeze
  • Boilerplate reduction. Less noise, more concise code.
  • First class support for JSON patches
  • Small: 3KB gzipped

Map Set (`website/docs/map-set.md`)


id: map-set title: Map and Set

โš  Since version 6 support for Maps and Sets has to be enabled explicitly by calling enableMapSet() once when starting your application.

Plain objects, arrays, Maps and Sets are always drafted by Immer. An example of using Maps with immer:

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"`
	)
})

Maps and Sets that are produced by Immer will be made artificially immutable. This means that they will throw an exception when trying mutative methods like set, clear etc. outside a producer.

Note: The keys of a map are never drafted! This is done to avoid confusing semantics and keep keys always referentially equal


Original (`website/docs/original.md`)


id: original title: Extracting the original state from a draft sidebar_label: Original

Immer exposes a named export original that will get the original object from the proxied instance inside produce (or return undefined for unproxied values). A good example of when this can be useful is when searching for nodes in a tree-like state using strict equality.

js
import {original, produce} from "immer"

const baseState = {users: [{name: "Richie"}]}
const nextState = produce(baseState, draftState => {
	original(draftState.users) // is === baseState.users
})

Just want to know if a value is a proxied instance? Use the isDraft function! Note that original cannot be invoked on objects that aren't drafts.

js
import {isDraft, produce} from "immer"

const baseState = {users: [{name: "Bobby"}]}
const nextState = produce(baseState, draft => {
	isDraft(draft) // => true
	isDraft(draft.users) // => true
	isDraft(draft.users[0]) // => true
})
isDraft(nextState) // => false

Other Lang (`website/docs/other-lang.md`)


id: other-lang title: Porting to other languages

Immer has been ported to other programming languages.

Language Link
Java Jimmer

Patches (`website/docs/patches.mdx`)


id: patches title: Patches

text
egghead.io lesson 14: Capture patches using _produceWithPatches_



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-capture-patches-to-distribute-changes-in-app-state-with-immer-producewithpatches"
>
	Hosted on egghead.io
</a>



	egghead.io lesson 16: Apply Patches using _applyPatches_



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-apply-patches-using-immer-applypatches-to-synchronize-state-across-clients"
>
	Hosted on egghead.io
</a>

โš  Since version 6 support for Patches has to be enabled explicitly by calling enablePatches() once when starting your application.

During the run of a producer, Immer can record all the patches that would replay the changes made by the reducer. This is a very powerful tool if you want to fork your state temporarily and replay the changes to the original.

Patches are useful in few scenarios:

  • To exchange incremental updates with other parties, for example over websockets
  • For debugging / traces, to see precisely how state is changed over time
  • As basis for undo/redo or as an approach to replay changes on a slightly different state tree

To help with replaying patches, applyPatches comes in handy. Here is an example how patches could be used to record the incremental updates and (inverse) apply them:

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
})

The generated patches are similar (but not the same) to the RFC-6902 JSON patch standard, except that the path property is an array, rather than a string. This makes processing patches easier. If you want to normalize to the official specification, use patch.path = "/" + patch.path.join("/") so the result is a JSON Pointer with a leading / (and escape ~ / / in path tokens per RFC-6901 when needed). Anyway, this is what a bunch of patches and their inverse could look like:

json
[
	{
		"op": "replace",
		"path": ["profile"],
		"value": {"name": "Veria", "age": 5}
	},
	{"op": "remove", "path": ["tags", 3]}
]
json
[
	{"op": "replace", "path": ["profile"], "value": {"name": "Noa", "age": 6}},
	{"op": "add", "path": ["tags", 3], "value": "kiddo"}
]

โš  Note: The set of patches generated by Immer should be correct, that is, applying them to an equal base object should result in the same end state. However Immer does not guarantee the generated set of patches will be optimal, that is, the minimum set of patches possible. It depends often on the use case what is considered 'optimal', and generating the optimal set of patches is potentially computationally very expensive. So in cases you might want to post process the generated patches, or compress them as explained below.

`produceWithPatches`

text
egghead.io lesson 19: Using inverse patches to build undo functionality



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-use-immer-inversepatches-to-build-undo-functionality"
>
	Hosted on egghead.io
</a>



	egghead.io lesson 20: Use patches to build redo functionality



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-use-immer-patches-to-build-redo-functionality"
>
	Hosted on egghead.io
</a>

Instead of setting up a patch listener, an easier way to obtain the patches is to use produceWithPatches, which has the same signature as produce, except that it doesn't return just the next state, but a tuple consisting of [nextState, patches, inversePatches]. Like produce, produceWithPatches supports currying as well.

javascript
import {produceWithPatches} from "immer"

const [nextState, patches, inversePatches] = produceWithPatches(
	{
		age: 33
	},
	draft => {
		draft.age++
	}
)

Which produces:

javascript
;[
	{
		age: 34
	},
	[
		{
			op: "replace",
			path: ["age"],
			value: 34
		}
	],
	[
		{
			op: "replace",
			path: ["age"],
			value: 33
		}
	]
]

For a more in-depth study, see Distributing patches and rebasing actions using Immer

Tip: Check this trick to compress patches produced over time.


Performance (`website/docs/performance.mdx`)


id: performance title: Immer performance

text
egghead.io lesson 5: Leveraging Immer's structural sharing in React



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-profile-react-rendering-and-optimize-with-memo-to-leverage-structural-sharing"
>
	Hosted on egghead.io
</a>



	egghead.io lesson 7: Immer will try to re-cycle data if there was no
	semantic change



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/javascript-produces-immutable-data-and-avoid-unnecessary-creation-of-new-data-trees-with-immer"
>
	Hosted on egghead.io
</a>

Here is a simple benchmark on the performance of Immer. This test takes 50,000 todo items and updates 5,000 of them. Freeze indicates that the state tree has been frozen after producing it. This is a development best practice, as it prevents developers from accidentally modifying the state tree.

Something that isn't reflected in the numbers above, but in reality, Immer is sometimes significantly faster than a hand written reducer. The reason for that is that Immer will detect "no-op" state changes, and return the original state if nothing actually changed, which can avoid a lot of re-renderings for example. Cases are known where simply applying immer solved critical performance issues.

These tests were executed on Node 10.16.3. Use yarn test:perf to reproduce them locally.

Most important observation:

  • Immer with proxies is roughly speaking twice to three times slower as a handwritten reducer (the above test case is worst case, see yarn test:perf for more tests). This is in practice negligible.
  • Immer is roughly as fast as ImmutableJS. However, the immutableJS + toJS makes clear the cost that often needs to be paid later; converting the immutableJS objects back to plain objects, to be able to pass them to components, over the network etc... (And there is also the upfront cost of converting data received from e.g. the server to immutable JS)
  • Generating patches doesn't significantly slow down immer
  • The ES5 fallback implementation is roughly twice as slow as the proxy implementation, in some cases worse.

Performance tips

Enable the Array Methods Plugin

For applications with significant array iteration within producers, enable the Array Methods Plugin:

javascript
import {enableArrayMethods} from "immer"
enableArrayMethods()

This plugin optimizes array operations like filter, find, some, every, and slice by avoiding proxy creation for every element during iteration. Without the plugin, iterating a 1000-element array creates 1000+ proxies. With the plugin, callbacks receive base values, and proxies are only created for elements you actually mutate.

Use loose iteration for better performance

By default, Immer uses loose iteration which only processes enumerable string properties. This is faster than strict iteration which includes symbols and non-enumerable properties. For most use cases, the default is optimal:

javascript
import {setUseStrictIteration} from "immer"

// Default: false (loose iteration for better performance)
setUseStrictIteration(false)

Only enable strict iteration if you specifically need to track symbol or non-enumerable properties.

Pre-freeze data

When adding a large data set to the state tree in an Immer producer (for example data received from a JSON endpoint), it is worth to call freeze(json) on the root of the data that is being added first. To shallowly freeze it. This will allow Immer to add the new data to the tree faster, as it will avoid the need to recursively scan and freeze the new data.

You can always opt-out

Realize that immer is opt-in everywhere, so it is perfectly fine to manually write super performance critical reducers, and use immer for all the normal ones. Even from within a producer you opt-out from Immer for certain parts of your logic by using utilies original or current and perform some of your operations on plain JavaScript objects.

For expensive search operations, read from the original state, not the draft

Immer will convert anything you read in a draft recursively into a draft as well. If you have expensive side effect free operations on a draft that involves a lot of reading, for example finding an index using find(Index) in a very large array, you can speed this up by first doing the search, and only call the produce function once you know the index. Thereby preventing Immer to turn everything that was searched for in a draft. Or, alternatively, perform the search on the original value of a draft, by using original(someDraft), which boils to the same thing.

Pull produce as far up as possible

Always try to pull produce 'up', for example for (let x of y) produce(base, d => d.push(x)) is exponentially slower than produce(base, d => { for (let x of y) d.push(x)})


Pitfalls (`website/docs/pitfalls.md`)


id: pitfalls title: Pitfalls

Performance tips

For performance tips, see Performance Tips.

Don't reassign the recipe argument

Never reassign the draft argument (example: draft = myCoolNewState). Instead, either modify the draft or return a new state. See Returning data from producers.

Immer only supports unidirectional trees

Immer assumes your state to be a unidirectional tree. That is, no object should appear twice in the tree, there should be no circular references. There should be exactly one path from the root to any node of the tree.

Never explicitly return `undefined` from a producer

It is possible to return values from producers, except, it is not possible to return undefined that way, as it is indistinguishable from not updating the draft at all! If you want to replace the draft with undefined, just return nothing from the producer.

Don't mutate exotic objects

Immer does not support exotic objects such as window.location.

Classes should be made draftable or not mutated

You will need to enable your own classes to work properly with Immer. For docs on the topic, check out the section on working with complex objects.

Only valid indices and length can be mutated on Arrays

For arrays, only numeric properties and the length property can be mutated. Custom properties are not preserved on arrays.

Data not originating from the state will never be drafted

Note that data that comes from the closure, and not from the base state, will never be drafted, even when the data has become part of the new draft.

javascript
function onReceiveTodo(todo) {
	const nextTodos = produce(todos, draft => {
		draft.todos[todo.id] = todo
		// Note, because 'todo' is coming from external, and not from the 'draft',
		// it isn't draft so the following modification affects the original todo!
		draft.todos[todo.id].done = true

		// The reason for this, is that it means that the behavior of the 2 lines above
		// is equivalent to code, making this whole process more consistent
		todo.done = true
		draft.todos[todo.id] = todo
	})
}

Immer patches are not necessarily optimal

The set of patches generated by Immer should be correct, that is, applying them to an equal base object should result in the same end state. However Immer does not guarantee the generated set of patches will be optimal, that is, the minimum set of patches possible.

Always use the result of nested producers

Nested produce calls are supported, but note that produce will always produce a new state. So even when passing a draft to a nested produce, the changes made by the inner produce won't be visible in the draft of the outer produce; those changes will only be visible in the output that the inner produce returns. In other words, when using nested produce, you get a draft of a draft and the result of the inner produce should be merged back into the original draft (or returned). For example produce(state, draft => { produce(draft.user, userDraft => { userDraft.name += "!" })}) won't work as the output of the inner produce isn't used. The correct way to use nested producers is:

javascript
produce(state, draft => {
	draft.user = produce(draft.user, userDraft => {
		userDraft.name += "!"
	})
})

Drafts aren't referentially equal

Draft objects in Immer are wrapped in Proxy, so you cannot use == or === to test equality between an original object and its equivalent draft (eg. when matching a specific element in an array). Instead, you can use the original helper:

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)

If possible, it's recommended to perform the comparison outside the produce function, or to use a unique identifier property like .id instead, to avoid needing to use original.

Array Methods Plugin: Callbacks receive base values

When using the Array Methods Plugin (enableArrayMethods()), callbacks for overridden methods like filter, find, some, every, and slice receive base values (not drafts). This is the core performance optimization - it avoids creating proxies for every element during iteration.

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
})

This only applies to methods intercepted by the plugin. Methods like map, forEach, reduce are NOT overridden and work normally - their callbacks receive drafts.


Produce (`website/docs/produce.mdx`)


id: produce title: Using produce

text
egghead.io lesson 3: Simplifying deep updates with _produce_



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/javascript-simplify-deep-state-updates-using-immer-produce"
>
	Hosted on egghead.io
</a>

The Immer package exposes a produce function that does all the work.

produce(baseState, recipe: (draftState) => void): nextState

produce takes a base state, and a recipe that can be used to perform all the desired mutations on the draft that is passed in. The interesting thing about Immer is that the baseState will be untouched, but the nextState will reflect all changes made to draftState.

Inside the recipe, all standard JavaScript APIs can be used on the draft object, including field assignments, delete operations, and mutating array, Map and Set operations like push, pop, splice, set, sort, remove, etc.

Any of those mutations don't have to happen at the root, but it is allowed to modify anything anywhere deep inside the draft: draft.todos[0].tags["urgent"].author.age = 56

Note that the recipe function itself normally doesn't return anything. However, it is possible to return in case you want to replace the draft object in its entirety with another object, for more details see returning new data.

Example

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
})
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])

For advanced TypeScript typing scenarios, see Using TypeScript or Flow.

Terminology

  • (base)state, the immutable state passed to produce
  • recipe: the second argument of produce, that captures how the base state should be "mutated".
  • draft: the first argument of any recipe, which is a proxy to the original base state that can be safely mutated.
  • producer. A function that uses produce and is generally of the form (baseState, ...arguments) => resultState

Note that it isn't strictly necessary to name the first argument of the recipe draft. You can name it anything you want, for example users. Using draft as a name is just a convention to signal: "mutation is OK here".


Resources (`website/docs/resources.md`)


id: resources title: External resources


Return (`website/docs/return.mdx`)


id: return title: Returning new data from producers

text
egghead.io lesson 9: Returning completely new state



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-return-completely-new-state-from-an-immer-producer"
>
	Hosted on egghead.io
</a>

It is not needed to return anything from a producer, as Immer will return the (finalized) version of the draft anyway. However, it is allowed to just return draft.

It is also allowed to return arbitrarily other data from the producer function. But only if you didn't modify the draft. This can be useful to produce an entirely new state. Some examples:

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
	}
})

Note: It is not possible to return undefined this way, as it is indistinguishable from not updating the draft! Read on...

Producing `undefined` using `nothing`

So, in general, one can replace the current state by just returning a new value from the producer, rather than modifying the draft. There is a subtle edge case however: if you try to write a producer that wants to replace the current state with undefined:

javascript
produce({}, draft => {
	// don't do anything
})

Versus:

javascript
produce({}, draft => {
	// Try to return undefined from the producer
	return undefined
})

The problem is that in JavaScript a function that doesn't return anything also returns undefined! So immer cannot differentiate between those different cases. So, by default, Immer will assume that any producer that returns undefined just tried to modify the draft.

However, to make it clear to Immer that you intentionally want to produce the value undefined, you can return the built-in token nothing:

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'

N.B. Note that this problem is specific for the undefined value, any other value, including null, doesn't suffer from this issue.

Tip: to be able to return nothing from a recipe when using TypeScript, the state's type must accept undefined as value.

Inline shortcuts using `void`

text
egghead.io lesson 10: Avoid accidental returns by using _void_



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-avoid-accidental-returns-of-new-state-by-using-the-void-keyword"
>
	Hosted on egghead.io
</a>

Draft mutations in Immer usually warrant a code block, since a return denotes an overwrite. Sometimes that can stretch code a little more than you might be comfortable with.

In such cases, you can use javascripts void operator, which evaluates expressions and returns undefined.

javascript
// Single mutation
produce(draft => void (draft.user.age += 1))

// Multiple mutations
produce(draft => void ((draft.user.age += 1), (draft.user.height = 186)))

Code style is highly personal, but for code bases that are to be understood by many, we recommend to stick to the classic draft => { draft.user.age += 1} to avoid cognitive overhead.


Support (`website/docs/support.md`)


id: support title: Supporting immer

Immer currently has 350.000 dependents on GitHub, and has almost 10.000.000 downloads per month. However, only the first 2 days of development has been sponsored (by Mendix), and all the development and maintenance after that has been a labor of love.

If you are enjoying Immer, and you are grateful for the package, or want to ensure its longevity, consider sponsoring the open collective on https://opencollective.com/immer or make a one time donation using PayPal.


Typescript (`website/docs/typescript.mdx`)


id: typescript title: Using TypeScript or Flow sidebar_label: TypeScript / Flow

text
egghead.io lesson 12: Immer + TypeScript



	

<a
	className="egghead-link"
	href="https://egghead.io/lessons/react-type-immutable-immer-data-with-typescript"
>
	Hosted on egghead.io
</a>

The Immer package ships with type definitions inside the package, which should be picked up by TypeScript and Flow out of the box and without further configuration.

The TypeScript typings automatically remove readonly modifiers from your draft types and return a value that matches your original type. See this practical example:

ts
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

This ensures that the only place you can modify your state is in your produce callbacks. It even works recursively and with ReadonlyArray.

Best practices

  1. Always define your states as readonly as much as possible. This best reflects the mental model and reality, since Immer will freeze all its returned values.
  2. You can use the utility type Immutable to recursively make an entire type tree read-only, e.g.: type ReadonlyState = Immutable<State>.
  3. Immer won't automatically wrap all returned types in Immutable if the original type of the input state wasn't immutable. This is to make sure it doesn't break code bases that don't use immutable types.

Typing reusable recipes

If you want to pass a recipe callback around, you can type it using Draft and allow either void or a replacement state as the return value.

ts
import {produce} from "immer"
import type {Draft} from "immer"

type Recipe<S> = (draft: Draft<S>) => void | S

function updateState<S>(
	setState: (updater: (prev: S) => S) => void,
	recipe: Recipe<S>
) {
	setState(prev => produce(prev, recipe))
}

React example

A common usage in React is to wrap setState with produce so callers can provide a typed recipe.

tsx
import {produce} from "immer"
import type {Draft} from "immer"
import {useCallback, useState} from "react"

type State = {
	foo: string
	bar: string
}

const initialState: State = {
	foo: "",
	bar: ""
}

type Recipe = (draft: Draft<State>) => void | State

export function Example() {
	const [state, setState] = useState<State>(initialState)

	const updateState = useCallback((recipe: Recipe) => {
		setState(prev => produce(prev, recipe))
	}, [])

	return (
		<button
			onClick={() => {
				updateState(draft => {
					draft.foo = "Muffin Man" // this recipe is typed
				})
			}}
			type="button"
		>
			Hello {state.foo}
		</button>
	)
}

Tips for curried producers

We try to inference as much as possible. So if a curried producer is created and directly passed to another function, we can infer the type from there. This works well with for example React:

typescript
import {Immutable, produce} from "immer"

type Todo = Immutable<{
	title: string
	done: boolean
}>

// later...

const [todo, setTodo] = useState<Todo>({
	title: "test",
	done: true
})

// later...

setTodo(
	produce(draft => {
		// draft will be strongly typed and mutable!
		draft.done = !draft.done
	})
)

When a curried producer isn't passed directly somewhere else, Immer can infer the state type from the draft argument. For example when doing the following:

typescript
// See below for a better solution!

const toggler = produce((draft: Draft<Todo>) => {
	draft.done = !draft.done
})

// typeof toggler = (state: Immutable<Todo>) => Writable<Todo>

Note that we did wrap the Todo type of the draft argument with Draft, because Todo is a readonly type. For non-readonly types this isn't needed.

For the returned curried function, toggler, We will narrow the input type to Immutable<Todo>, so that even though Todo is a mutable type, we will still accept an immutable todo as input argument to toggler.

In contrast, Immer will widen the output type of the curried function to Writable<Todo>, to make sure it's output state is also assignable to variables that are not explicitly typed to be immutable.

This type narrowing / widening behavior might be unwelcome, maybe even for the simple reason that it results in quite noisy types. So we recommend to specify the generic state type for curried producers instead, in cases where it cannot be inferred directly, like toggler above. By doing so the automatic output widening / input narrowing will be skipped. However, the draft argument itself will still be inferred to be a writable Draft<Todo>:

typescript
const toggler = produce<Todo>(draft => {
	draft.done = !draft.done
})

// typeof toggler = (state: Todo) => Todo

However, in case the curried producer is defined with an initial state, Immer can infer the state type from the initial state, so in that case the generic doesn't need to be specified either:

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

In case the toggler has no initial state, and it has curried arguments, and you set the state generic explicitly, then type of any additional arguments should be defined explicitly as a tuple type as well:

typescript
const toggler = produce<Todo, [boolean]>((draft, newState) => {
	draft.done = newState
})

// typeof toggler = (state: Todo, newState: boolean) => Todo

Cast utilities

The types inside and outside a produce can be conceptually the same, but from a practical perspective different. For example, the State in the examples above should be considered immutable outside produce, but mutable inside produce.

Sometimes this leads to practical conflicts. Take the following example:

typescript
type Todo = {readonly done: boolean}

type State = {
	readonly finishedTodos: readonly Todo[]
	readonly unfinishedTodos: readonly Todo[]
}

function markAllFinished(state: State) {
	produce(state, draft => {
		draft.finishedTodos = state.unfinishedTodos
	})
}

This will generate the error:

text
The type 'readonly Todo[]' is 'readonly' and cannot be assigned to the mutable type '{ done: boolean; }[]'

The reason for this error is that we assign our read only, immutable array to our draft, which expects a mutable type, with methods like .push etc etc. As far as TS is concerned, those are not exposed from our original State. To hint TypeScript that we want to upcast the collection here to a mutable array for draft purposes, we can use the utility castDraft:

draft.finishedTodos = castDraft(state.unfinishedTodos) will make the error disappear.

There is also the utility castImmutable, in case you ever need to achieve the opposite. Note that these utilities are for all practical purposes no-ops, they will just return their original value.

Tip: You can combine castImmutable with produce to type the return type of produce as something immutable, even when the original state was mutable:

typescript
// a mutable data structure
const baseState = {
	todos: [{
		done: false
	}]
}

const nextState = castImmutable(produce(baseState, _draft => {}))

// inferred type of nextState is now:
{
	readonly todos: ReadonlyArray<{
		readonly done: boolean
	}>
})

Compatibility

Note: Immer v5.3+ supports TypeScript v3.7+ only.

Note: Immer v3.0+ supports TypeScript v3.4+ only.

Note: Immer v1.9+ supports TypeScript v3.1+ only.

Note: Flow support might be removed in future versions and we recommend TypeScript


Update Patterns (`website/docs/update-patterns.md`)


id: update-patterns title: Update patterns

Working with immutable data, before Immer, used to mean learning all the immutable update patterns.

To help 'unlearning' those patterns here is an overview how you can leverage the built-in JavaScript APIs to update objects and collections:

Object mutations

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 {};
})

Any time a nested draft field gets a new reference or value, produce() will finish applying the immutable update and return a new reference. If you tried to mutate, but the values remained the same, Immer will bail out and return the existing reference from produce()

Array mutations

text
/* Detailed source-code truncated for AI context efficiency. */

Nested data structures

javascript
import {produce} from "immer"

// example complex data structure
const store = {
	users: new Map([
		[
			"17",
			{
				name: "Michel",
				todos: [
					{
						title: "Get coffee",
						done: false
					}
				]
			}
		]
	])
}

// updating something deeply in-an-object-in-an-array-in-a-map-in-an-object:
const nextStore = produce(store, draft => {
	draft.users.get("17").todos[0].done = true
})

// filtering out all unfinished todo's
const nextStore = produce(store, draft => {
	const user = draft.users.get("17")
	// when filtering, creating a fresh collection is simpler than
	// removing irrelevant items
	user.todos = user.todos.filter(todo => !todo.done)
})

Note that many array operations can be used to insert multiple items at once by passing multiple arguments or using the spread operation: todos.unshift(...items).

Note that when working with arrays that contain objects that are typically identified by some id, we recommend to use Map or index based objects (as shown above) instead of performing frequent find operations, lookup tables perform much better in general.


--- METRICS ---