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:
- 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).
- Navigate Backwards: Revert the application to a previously recorded state.
- Navigate Forwards: Move forward through states that were previously navigated away from using the "back" functionality.
- 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
goBackandgoForward. - 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
goBackorgoForwardis 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):
- Initial State:
{ count: 0 } - User increments counter: State pushed:
{ count: 1 } - User increments counter: State pushed:
{ count: 2 } - 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:
- User calls
goForward() - 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):
- Initial State:
{ data: 'A' } - State pushed:
{ data: 'B' } - State pushed:
{ data: 'C' } - User calls
goBack() - 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
pushStatefunction. 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
historyStackand thecurrentIndexcorrectly, 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".