How Does the Undo/Redo Functionality Work with Pinia Store in Vue Color Avatar

The vue-color-avatar editor implements time-travel history using a Pinia store that maintains past, present, and future arrays, enabling unlimited undo and redo through state mutation actions.

The vue-color-avatar repository provides a vector-style avatar generator built with Vue 3 and TypeScript. Understanding how the undo/redo functionality works with Pinia store reveals a classic command-pattern implementation that tracks state changes through immutable history stacks.

Understanding the History State Structure

The Pinia store defined in src/store/index.ts structures history as three distinct collections that represent the timeline of user interactions.

Property Purpose
past Array of previous AvatarOption values in chronological order
present The current AvatarOption actively displayed in the UI
future Array of AvatarOption values that were undone and can be restored
// src/store/index.ts (lines 16-20)
export interface State {
  history: {
    past: AvatarOption[]
    present: AvatarOption
    future: AvatarOption[]
  }
  isSiderCollapsed: boolean
}

Recording State Changes with SET_AVATAR_OPTION

When users modify avatar settings, the [SET_AVATAR_OPTION] action updates the history stack. This action implements the core logic for branching timelines by clearing the future array whenever a new state is committed.

// src/store/index.ts (lines 35-41)
[SET_AVATAR_OPTION](data: AvatarOption) {
  this.history = {
    past: [...this.history.past, this.history.present],
    present: data,
    future: [],
  }
}

Key behavior: The spread operator creates immutable copies of the past array, ensuring Reactivity works correctly while the future array resets to empty. This prevents redo operations from jumping to obsolete states after new modifications.

Implementing Undo and Redo Actions

The store exposes [UNDO] and [REDO] actions that manipulate the three history collections to navigate through the state timeline.

The UNDO Action

The [UNDO] action moves the current state into the future stack and restores the most recent previous state.

// src/store/index.ts (lines 43-52)
[UNDO]() {
  if (this.history.past.length > 0) {
    const previous = this.history.past[this.history.past.length - 1]
    const newPast = this.history.past.slice(0, this.history.past.length - 1)
    this.history = {
      past: newPast,
      present: previous,
      future: [this.history.present, ...this.history.future],
    }
  }
}

Execution flow:

  1. Verify past contains at least one entry
  2. Extract the last element as the previous state
  3. Slice past to remove the retrieved element
  4. Prepend current present to future array
  5. Assign previous as new present

The REDO Action

The [REDO] action reverses an undo by shifting states from future back to present and pushing the current state onto past.

// src/store/index.ts (lines 55-64)
[REDO]() {
  if (this.history.future.length > 0) {
    const next = this.history.future[0]
    const newFuture = this.history.future.slice(1)
    this.history = {
      past: [...this.history.past, this.history.present],
      present: next,
      future: newFuture,
    }
  }
}

Execution flow:

  1. Verify future contains at least one entry
  2. Extract the first element as the next state
  3. Slice future to remove the retrieved element
  4. Append current present to past array
  5. Assign next as new present

Integrating Undo/Redo in Vue Components

Components interact with the history system through the store's typed actions and reactive state. The mutation constants in src/store/mutation-type.ts provide type-safe access to action names.

// Component integration example
import { useStore } from '@/store'
import { SET_AVATAR_OPTION, UNDO, REDO } from '@/store/mutation-type'

const store = useStore()

// Apply a new avatar configuration
function updateAvatar(option: AvatarOption) {
  store[SET_AVATAR_OPTION](option)
}

// History navigation handlers
function handleUndo() {
  store[UNDO]()
}

function handleRedo() {
  store[REDO]()
}

Checking History Availability

UI controls should disable undo/redo buttons when the respective history stacks are empty. Compute these states using the past and future array lengths.

import { computed } from 'vue'

const canUndo = computed(() => store.history.past.length > 0)
const canRedo = computed(() => store.history.future.length > 0)

Summary

  • The Pinia store in src/store/index.ts implements undo/redo through three reactive arrays: past, present, and future.
  • Immutable updates using the spread operator ensure Reactivity works correctly when shifting states between history stacks.
  • Branching timeline logic clears the future array whenever [SET_AVATAR_OPTION] commits a new state, preventing invalid redo operations.
  • Type-safe actions use constants from src/store/mutation-type.ts to expose [UNDO] and [REDO] methods that manipulate the history collections.
  • UI integration relies on computed properties checking store.history.past.length and store.history.future.length to enable or disable history controls.

Frequently Asked Questions

How is the history state initialized in the Pinia store?

The history state initializes with an empty past array, a present value generated by getRandomAvatarOption() from src/utils/index.ts, and an empty future array. This setup provides a valid initial avatar configuration while maintaining empty history stacks ready to record user interactions.

Why does the future array get cleared when setting a new avatar option?

The [SET_AVATAR_OPTION] action clears the future array to maintain a linear timeline. When users undo several steps and then make a new change, the previous future states become invalid because they represent a timeline that no longer exists. Clearing future prevents the UI from offering redo operations that would jump to obsolete configurations.

Can the undo/redo history be limited to a specific number of steps?

The current implementation in src/store/index.ts does not impose a limit on the past or future array lengths. However, you could extend the [SET_AVATAR_OPTION] action to check this.history.past.length and use slice() to keep only the most recent N entries, preventing memory growth during long editing sessions.

How do I check if undo or redo actions are available in the UI?

Create computed properties that evaluate the length of the history arrays. Check store.history.past.length > 0 to determine if undo is available, and store.history.future.length > 0 for redo availability. Bind these booleans to the disabled attributes of your undo and redo buttons to provide clear visual feedback to users.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →