Implementing Undo-Redo Functionality in Redux using Immer

Got a requirement to implement an undo-redo functionality in your frontend application that is built during Redux and if you are looking for the cleaner approach then you are in the right place, keep reading!

What is Immer?

To put it in simple terms, Immer is a library that allows you to easily work with immutable data, in such a way that you won’t even feel you are working with immutable data. If you are not familiar with Immer already, I highly recommend you to check out their official page here before you continue.

That being said, Immer brings in a feature called Patches. That allows us to implement many cool features – one of them being Undo/Redo.

So, What are Patches?

Immer records all the changes that you perform on the draft object and generates an array of JSON objects indicating only what has changed. These arrays are called Patches. Immer also produces inverse patches which are basically patches that need to be applied if you want to go back to the previous state.

Example:
Let’s say we have a state like this and you want to update the age to 40, this is how you would do it normally.

To get our hands on the generated patches, we can use the third argument of the produce function which is a callback function that will receive both patches and inverse patches.

These patches are ideal fit to implement an Undo-Redo in Redux. So that’s exactly what we will do in the following section.

Implementing Undo-Redo in Redux using Immer Patches

For illustration purpose, we will create a very simple reducer for a Todo List like so:

To add the undo/redo functionality, we will first create a hash and store the patches and inverse patches.

We may not want to allow an infinite number of undo/redo as it requires us to store an infinite number of patches and inverse patches. At some point, it will put too much load on the browser. So we can put a hard limit on the number of undo/redo like below:

Also, we don’t want to record changes triggered by every action. So we will filter out patches and inverse patches based on the action received.

Now that we have all our patches and inverse patches ready, all that’s left for us to do is apply these patches when the user triggers undo/redo action. To apply the patches, we can use the applyPatches function provided by Immer.

Finally, we should enable the undo/redo buttons only when we have a patch available for it. To do that, we will add two more fields to our redux state – canUndo and canRedo and we will set these flags based on the following rules:

  1. When we receive an undoable action – we can always undo but cannot redo anymore. That’s how undo-redo works.
  2. Whenever you perform an undo – you can always redo and keep doing undo as long as we have a patch for it.
  3. Whenever you redo – you can always undo and keep doing redo as long as we have a patch for it.

applyPatches returns a new state, to update the flags immutably on the new state we will pass it to produce and then perform the update on the newDraft.

That’s it, we have a full implementation of undo-redo in Redux with a configurable limit and actions. I hope it was helpful to you.

Written By
Praveen
Programmer | Tech Blogger | Multitasker | Learner Forever | Someone who believes knowledge grows by sharing | Looking forward to using spare time productively
Write the first response