Returning new data from producers
egghead.io lesson 9: Returning completely new state
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:
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 return
ing 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
:
produce({}, draft => {
// don't do anything
})
Versus:
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
:
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
​
egghead.io lesson 10: Avoid accidental returns by using _void_
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
.
// 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.