If you're working with Redux, it's important to keep in mind that reducers are pure functions. A pure function is a type of function that only returns a specific expected result based on an input. This means that if
x is the input, it will always return
A reducer takes the previous state and an action and return the next state. Why is this important? This is because a reduce returns a new state object rather than mutating the previous state.
Here is an example piece of code that shows what you cannot do in a reducer:
ADD_TODO action, pushing a todo object into the state using the push method causes mutation to the original
state. This makes the reducer impure because it changed the original object.
For a reducer to remain pure, you'd need to prevent change on the original object's
state. This is where enforcing immutability comes in.
Let's take a look at the following object:
If we want to maintain immutability and update
education.school.name, we can do so by making a copy of this object. One way to achieve this is by using the object spread. Here is an example:
In the code sample above, we created a copy of the
User object and changed the
education.school.name using object destructuring. While this gets the job done, it's hard to read. We'd need to go deeper into the object tree to change the property.
As the object grows, so does the complexity of creating copies to preserve immutability.
This is where immer.js comes in.
What is immer.js?
immer.js is a library that allows you to work with immutable states without breaking immutability itself. This is achieved through a copy-on-write mechanism. Here is a three-step process flow of how immer.js works:
- create a
draftStatewhich is a temporary state of your object's
- apply all the changes to this
- return the final state based on the
draftStatewhile still keeping the original object unchanged.
You can install immer.js via
If your application is not using any of these package managers, you can directly add immer.js to your application through CDN like so.
Once installed, you can start using immer.js right away.
The immer package exposes a default function called
produce that does all the work and is a source of truth. As the name of the function suggests, it "produces" the
nextState based on the
currentState of your object/array.
Here's how the definition of
produce function looks like.
It accepts two parameters:
currentState- the current state of the object/array.
producer- an anonymous function that receives the
draftStateas its only argument and on which you can perform all the operations.
If we want to rewrite our previous example using immmer.js, we can do it like so.
The above method is much easier to deal with than using nested spread operators. We are now able to copy and mutate the
Here is the original syntax for comparison.
You can also work with arrays the same way objects. Here is an example:
If you're working with functional components in your React.js application, you can use this complementary hook called
useImmer to manipulate state inside your functional components.
userImmer, you will need to install it, in addition to having
Once installed, you can start using the
useImmer hook in your application just like the
useState hook. With
useImmer, you can directly mutate the state inside the function that updates the state.
Here is an example:
updatePerson that we get from
useImmer sits on top of immer.js. This means you can directly mutate the state inside
updatePerson just like how you would do using the
produce function of immer.js.
Simplifying Redux reducers using immer.js
Using the curried producers of immer.js, you can greatly simplify your redux reducer logic.
Passing a function as the first argument to produce is intended to be used for currying. This means that you get a pre-bound producer that only needs a state to produce the value from. The producer function gets passed in the draft and any further arguments that were passed to the curried function.
Here is an example:
You can simplify this reducer by using immer's curried producers like so.
draft is the copy of the original state and
action will be the regular action that this reducer will receive.
As you can tell, you can also provide an initial state (
INITIAL_STATE) as the second argument to the
produce function. This lets you avoid writing the
immer.js is very powerful through its ability to help us simplify our code without sacrificing functionality. Overall, it can reduce the amount of code we need to write, maintain code comprehension and ensure that the code we write do not accidentally introduce side effects through the mutability of the original array or object.