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.
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:
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:
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
.