Create a Vuex Undo/Redo Plugin for Vue.js
Create a Vuex Undo/Redo Plugin for Vue.js
In this article, I'll demonstrate how to create an undo/redo feature with Vuex, which works in a similar way to time-travel debugging.
Join the DZone community and get the full member experience.Join For Free
There are many benefits to centralizing your application state in a Vuex store. One benefit is that all transaction are recorded. This allows for handy features like time-travel debugging where you can jump between previous states to isolate problems.
In this article, I'll demonstrate how to create an undo/redo feature with Vuex, which works in a similar way to time-travel debugging. This feature could be used in a variety of scenarios from complex forms to browser-based games.
Setting Up a Plugin
To make this feature reusable we'll create it as a Vue plugin. This feature requires us to add some methods and data to the Vue instance, so we'll structure the plugin as a mixin.
To use it in a project we can simply import the plugin and install it:
You may also like: Components and How They Interact in Vue and Vuex.
The feature will work by rolling back the last mutation if the user wants to undo, then re-applying it if they want to redo. How will we implement this?
The first possible approach is to "snapshot" the state of the store after every mutation and pushing the snapshot into an array. To undo/redo we can grab the correct snapshot and replace the store state with it.
The snapshot approach would require that you first make a clone of the state before pushing. Given that Vue state is made reactive through the automatic additions of getter and setter functions, it doesn't play nicely with cloning.
Another possible approach is to log every mutation that is committed. To undo, we reset the store to its initial state and then re-run the mutations again; all but the last. Redoing is a similar concept.
Given the principles of Flux, re-running the mutations from the same initial state should recreate the state perfectly. Since this is a cleaner approach than the first, let's proceed with it.
Vuex offers an API method for subscribing to mutations which we can use to log them. We'll set this up in the
created hook. In the callback, we simply push the mutation into an array which can later be re-run.
To undo a mutation, we will clear the store then re-run all the mutations except for the last one. Here's how the code works:
- Use the
poparray method to remove the last mutation.
- Clear the store state with a special mutation
- Iterate each remaining mutation, committing it again to the fresh store. Note that the subscribe method is still active during this process, meaning each mutation will keep being re-added. Remove it again immediately with
popto prevent this.
Clearing the Store
Whenever this plugin is used the developer must implement a mutation in their store called
emptyState. This has the job of reverting the store back to its original state so it's ready to be re-built from scratch.
The developer must do this themselves because the plugin we're building doesn't have access to the store, only the Vue instance. Here's an example implementation:
Going back to our plugin, the
emptyState mutation should not be added to our
done list, as we don't want to re-commit that in the undo process. Prevent this with the following logic:
Let's create a new data property
undone which will be an array. When we remove the last mutation from
done during the undo process, we push it to this array:
We can now create a
redo method which will simply take the last mutation added to
undone and re-commit it.
If the user triggers an undo one or more times, then makes a fresh new commit, the contents of
undone will be invalidated. If this happens we should empty
We can detect new commits from within our subscribe callback when a commit is added. The logic is tricky, though, as the callback doesn't have any obvious way of knowing what is a new commit, and what is an undo/redo commit.
The simplest approach is to set a flag
newMutation in the instance. This will be true by default, but the undo and redo methods will temporarily set it to false. If it is set to true when a mutation is committed, the
subscribe callback will clear the
The main functionality is now complete! Add the plugin to your own project, or to my demo to test it out.
You'll notice in my demo that the undo and redo buttons are disabled whenever their functionality is not currently allowed. For example, if there haven't been any commits yet, you obviously can't undo or redo. A developer using this plugin may want to implement similar functionality.
To allow this, the plugin can provide two computed properties
canRedo as part of a public API. These are trivial to implement:
Published at DZone with permission of Anthony Gore , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.