Hone logo
Hone
Problems

Implement a Vue History Manager

This challenge requires you to build a system that allows users to navigate through different states of a Vue application, similar to a browser's back and forward functionality. This is crucial for creating a more intuitive and user-friendly experience, especially in single-page applications where traditional browser history isn't automatically managed.

Problem Description

You need to create a Vue component and a service (or a composable) that manages the application's history. This system should allow users to:

  1. Record States: Capture the current state of the application at specific points in time. A "state" can be defined as any relevant data that represents a screen or a view within your application (e.g., current page, filter settings, selected item ID, form data).
  2. Navigate Backwards: Revert the application to a previously recorded state.
  3. Navigate Forwards: Move forward through states that were previously navigated away from using the "back" functionality.
  4. Clear History: Optionally, a way to reset the history.

Key Requirements

  • State Representation: The history manager should be flexible enough to store various types of application states. For simplicity, states can be represented as JSON-serializable objects.
  • Navigation Logic: Implement methods for goBack and goForward.
  • Current State Tracking: The system must always know what the "current" active state is.
  • Vue Integration: The history management should integrate seamlessly with a Vue 3 application using TypeScript. Consider using a composable function for reusability.
  • Undo/Redo: The "back" and "forward" actions are essentially undo/redo operations on the application's state.

Expected Behavior

  • When a user performs an action that changes the application's significant state, a new state should be pushed onto the history stack.
  • Calling goBack() should decrement the current state index and update the application to reflect the state at that index.
  • Calling goForward() should increment the current state index and update the application to reflect the state at that index.
  • If goBack() is called when at the earliest state, nothing should happen.
  • If goForward() is called when at the latest state, nothing should happen.
  • Navigating back and then recording a new state should discard any forward history.

Edge Cases to Consider

  • Initial State: How is the initial state of the application handled?
  • Empty History: What happens when goBack or goForward is called on an empty history?
  • History Limit: While not strictly required for this challenge, consider if a maximum history size would be a useful feature (and how it might be implemented).
  • State Comparison: If the current state is identical to the last recorded state, should it be pushed again? (For this challenge, assume it should be pushed unless specified otherwise).

Examples

Example 1:

Scenario: A simple counter application.

Input (Sequence of Actions & States Recorded):

  1. Initial State: { count: 0 }
  2. User increments counter: State pushed: { count: 1 }
  3. User increments counter: State pushed: { count: 2 }
  4. User decrements counter: State pushed: { count: 1 }

History State after Actions:

  • historyStack: [{ count: 0 }, { count: 1 }, { count: 2 }, { count: 1 }]
  • currentIndex: 3 (points to { count: 1 })

Output (After calling goBack()):

  • historyStack: [{ count: 0 }, { count: 1 }, { count: 2 }, { count: 1 }]
  • currentIndex: 2 (points to { count: 2 })
  • Application state updates to reflect { count: 2 }

Explanation: The goBack() call moved the currentIndex from 3 to 2, making the state { count: 2 } the active one.

Example 2:

Scenario: Continuing from Example 1.

Input (Current State):

  • historyStack: [{ count: 0 }, { count: 1 }, { count: 2 }, { count: 1 }]
  • currentIndex: 2 (points to { count: 2 })

Actions:

  1. User calls goForward()
  2. User increments counter: State pushed: { count: 3 }

History State after Actions:

  • After goForward():
    • historyStack: [{ count: 0 }, { count: 1 }, { count: 2 }, { count: 1 }]
    • currentIndex: 3 (points to { count: 1 })
    • Application state updates to reflect { count: 1 }
  • After pushing new state { count: 3 }:
    • historyStack: [{ count: 0 }, { count: 1 }, { count: 2 }, { count: 1 }, { count: 3 }]
    • currentIndex: 4 (points to { count: 3 })
    • Application state updates to reflect { count: 3 }

Explanation: The goForward() moved the currentIndex to 3. Then, pushing the new state { count: 3 } added it to the stack and updated currentIndex to 4. Crucially, the previous forward history (the state { count: 1 } at index 3) is effectively discarded because the new state is added after the current index.

Example 3:

Scenario: Navigating back and then trying to go forward.

Input (Sequence of Actions & States Recorded):

  1. Initial State: { data: 'A' }
  2. State pushed: { data: 'B' }
  3. State pushed: { data: 'C' }
  4. User calls goBack()
  5. User calls goBack()

History State after Actions:

  • Initial: [{ data: 'A' }], currentIndex: 0
  • After step 2: [{ data: 'A' }, { data: 'B' }], currentIndex: 1
  • After step 3: [{ data: 'A' }, { data: 'B' }, { data: 'C' }], currentIndex: 2
  • After step 4 (goBack()): [{ data: 'A' }, { data: 'B' }, { data: 'C' }], currentIndex: 1 (Application shows { data: 'B' })
  • After step 5 (goBack()): [{ data: 'A' }, { data: 'B' }, { data: 'C' }], currentIndex: 0 (Application shows { data: 'A' })

Output (After calling goForward() on the state from step 5):

  • historyStack: [{ data: 'A' }, { data: 'B' }, { data: 'C' }]
  • currentIndex: 0 (points to { data: 'A' })

Explanation: Calling goForward() when currentIndex is 0 (the earliest state) has no effect. The history remains unchanged, and the application stays at state { data: 'A' }.

Constraints

  • The history stack can store up to 100 states. If more states are pushed, the oldest state should be removed.
  • States must be JSON-serializable objects.
  • The solution should be implemented in TypeScript for a Vue 3 application.
  • The history management logic should be encapsulated in a reusable composable function.

Notes

  • Consider how you will make the current state reactive for your Vue components.
  • Think about how to trigger the pushState function. This could be via a watcher on a relevant data property or an explicit call from your components/actions.
  • The core challenge is managing the historyStack and the currentIndex correctly, especially when new states are added after navigating back.
  • For demonstrating the functionality, you can create a simple Vue component that displays a state and has buttons for "Add State", "Back", and "Forward".
Loading editor...
typescript