Javascript is required
Michael Hoffmann LogoMichael Hoffmann

Pinia Tip: Simple History With Undo and Redo Functionality

Michael Hoffmann - Senior Frontend Developer (Freelancer)
Michael Hoffmann
Nov 10, 2023
1 min read
|
16 views
Pinia
State
Pinia Tip: Simple History With Undo and Redo Functionality Image

I recently needed to implement a simple history with undo and redo functionality for a project I'm working on. I decided to use the useRefHistory composable from VueUse to implement this functionality.

Defining the store

Let's first take a look at the store we use for demonstration purposes which is a simple counter store:

stores/counter.ts
1
import { ref } from 'vue';
2
import { defineStore } from 'pinia';
3
4
export const useCounterStore = defineStore('counter', () => {
5
const count = ref(0);
6
7
function increment() {
8
count.value++;
9
}
10
11
function decrement() {
12
count.value--;
13
}
14
15
return { count, increment, decrement };
16
});

Now let's use that store in a Vue component:

components/Counter.vue
1
<script setup lang="ts">
2
const counterStore = useCounterStore();
3
4
const { count } = storeToRefs(counterStore);
5
</script>
6
7
<template>
8
<div class="container">
9
<span>count is {{ counterStore.count }}</span>
10
<button @click="counterStore.increment">Increment</button>
11
<button @click="counterStore.decrement">Decrement</button>
12
</div>
13
</template>

Implementing the history

Now that we have a store, let's implement the history functionality. We can do this by using the useRefHistory composable from VueUse:

components/Counter.vue
1
<script setup lang="ts">
2
const counterStore = useCounterStore();
3
4
const { count } = storeToRefs(counterStore);
5
6
const { history, undo, redo } = useRefHistory(count, {
7
capacity: 50, // limit history records
8
});
9
</script>
10
11
<template>
12
<div class="container">
13
<span>count is {{ counterStore.count }}</span>
14
<button @click="counterStore.increment">Increment</button>
15
<button @click="counterStore.decrement">Decrement</button>
16
</div>
17
18
<div class="container" style="margin-top: 20px">
19
<span>History</span>
20
<button @click="undo">Undo</button>
21
<button @click="redo">Redo</button>
22
23
<ul>
24
<li v-for="entry of history" :key="entry.timestamp">
25
<span>{{ entry }}</span>
26
</li>
27
</ul>
28
</div>
29
</template>

Demo

Try it yourself in the following StackBlitz:


If you liked this Vue tip, follow me on Twitter to get notified about new tips, blog posts, and more. Alternatively (or additionally), you can subscribe to my weekly Vue newsletter:

New Vue & Nuxt tips delivered to your inbox:
I will never share any of your personal data.