JAVASCRIPT

Building a Reusable Undo/Redo Composable in Vue 3

Learn to create a Vue 3 Composition API composable for managing an undo/redo history stack, providing robust state management for interactive applications.

// composables/useUndoRedo.js
import { ref, watch, computed } from 'vue';

export function useUndoRedo(initialValue) {
  const history = ref([initialValue]);
  const currentIndex = ref(0);
  const currentValue = ref(initialValue);

  watch(currentValue, (newValue) => {
    if (newValue !== history.value[currentIndex.value]) {
      // If a new change occurs after undoing, truncate future history
      history.value = history.value.slice(0, currentIndex.value + 1);
      history.value.push(newValue);
      currentIndex.value = history.value.length - 1;
    }
  }, { deep: true });

  const canUndo = computed(() => currentIndex.value > 0);
  const canRedo = computed(() => currentIndex.value < history.value.length - 1);

  const undo = () => {
    if (canUndo.value) {
      currentIndex.value--;
      currentValue.value = history.value[currentIndex.value];
    }
  };

  const redo = () => {
    if (canRedo.value) {
      currentIndex.value++;
      currentValue.value = history.value[currentIndex.value];
    }
  };

  const commit = (newValue) => {
    currentValue.value = newValue;
  };

  return {
    currentValue,
    commit,
    undo,
    redo,
    canUndo,
    canRedo,
  };
}

<!-- App.vue -->
<template>
  <div>
    <h1>Undo/Redo Composable Example</h1>
    <textarea v-model="textValue" rows="5" cols="50"></textarea>
    <p>Current value: {{ textValue }}</p>
    <div>
      <button @click="undo" :disabled="!canUndo">Undo</button>
      <button @click="redo" :disabled="!canRedo">Redo</button>
    </div>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue';
import { useUndoRedo } from './composables/useUndoRedo';

const { currentValue, commit, undo, redo, canUndo, canRedo } = useUndoRedo('Initial text');

// Bind the textarea to currentValue, which is watched by the composable
// Use an explicit watcher for a controlled commit to the undo history
// Alternatively, 'v-model' directly on 'currentValue' would work for simpler cases
const textValue = ref(currentValue.value);
watch(textValue, (newValue) => {
  commit(newValue);
});
</script>
How it works: This snippet showcases a powerful use of Vue 3's Composition API: a reusable composable function for managing an undo/redo state. The `useUndoRedo` composable tracks changes to a given value, storing its history in an array. It provides `undo` and `redo` functions, along with `canUndo` and `canRedo` computed properties to control button states. This pattern allows for encapsulating complex stateful logic that can be easily reused across multiple components, promoting cleaner, more maintainable code, similar to how hooks function in React but with Vue's reactivity system.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs