When building dynamic interfaces, especially where users can make changes (text editors, or design tools), providing undo and redo functionality dramatically improves user experience.
Thankfully, Vue + VueUse makes this a breeze with the useRefHistory
composable.
🔄 What is useRefHistory?
useRefHistory
from the @vueuse/core package allows you to track the history of changes to a ref in Vue. It enables you to programmatically undo or redo changes made to that ref, just like in text editors or drawing apps.
💡 Let's Build a Simple To-do App with Undo/Redo
In this example, we have a list of to-do items. Every time you add or remove an item, the change is tracked — and you can go back and forth using Undo and Redo buttons.
🧠 The Key Code:
<script setup>
import { ref } from "vue";
import { useRefHistory } from "@vueuse/core";
const todoItems = ref(["walk my dog", "go to the gym", "eat lunch"]);
const add = (e) => {
todoItems.push(e.target.value);
e.target.value = "";
};
const remove = (index) => todoItems.value.splice(index, 1);
// 👇 Track changes to `todoItems` and enable undo/redo
const { undo, redo } = useRefHistory(todoItems, {
deep: true,
});
</script>
<template>
<div>
<button @click="undo">Undo</button>
<button @click="redo">Redo</button>
<input type="text" @keyup.enter="add" placeholder="Add todo" />
<ul>
<li v-for="(item, index) in todoItems" :key="item">
{{ item }}
<button @click="remove(index)">Remove</button>
</li>
</ul>
</div>
</template>
Here it is in action:
🛠 Use Cases for useRefHistory
Here are a few places where useRefHistory can be a game changer:
Forms: Revert user inputs step-by-step (e.g., a multi-field form with preview).
Design Tools: Let users undo shapes, colors, or movements in a canvas.
Text Editors: Implement undo/redo in markdown or WYSIWYG editors.
Games: Go back to previous states (e.g., board games like chess).
Collaborative Tools: Temporarily revert shared content locally before syncing changes.
📝 Final Thoughts
useRefHistory
is a simple yet powerful utility that adds professional polish to your Vue apps. Whether you're building productivity tools or just want to offer a better user experience, this is definitely one to have in your toolbox.
Want to see the full code? Try it yourself here!
Top comments (2)
Amazing explanation and very easy to understand! How could this feature be expanded to support larger state changes efficiently?
It works quite well for even larger state changes, and you can even define your own clone function that goes past
x => JSON.parse(JSON.stringify(x))
which is what useRefHistory uses by default. Check the docs here