Implementing a useStateHistory Hook in React
The useStateHistory hook provides a way to track and revert to previous states of a React component's state. This is particularly useful for implementing undo/redo functionality, debugging, or creating more complex state management scenarios where you need to maintain a history of state changes. This challenge asks you to implement this hook from scratch.
Problem Description
You are tasked with creating a custom React hook called useStateHistory. This hook should extend the functionality of the built-in useState hook by maintaining a history of state changes. The hook should accept an initial state value as an argument and return an array containing:
- The current state value.
- A function to update the state (similar to
setStateinuseState). - A function to undo the last state change.
- A function to redo the last undone state change.
- The current history index (an integer representing the position of the current state in the history array).
Key Requirements:
- History Tracking: Every time the state is updated using the provided update function, the previous state should be pushed onto a history array.
- Undo Functionality: The
undofunction should revert the state to the previous state in the history, decrementing the history index. - Redo Functionality: The
redofunction should revert the state to the next state in the history (if available), incrementing the history index. - History Limit: The history array should have a maximum size. When a new state is added and the history is full, the oldest state should be removed.
- Immutability: State updates should be handled immutably. The history array should store copies of the state, not references.
- Edge Cases:
- Calling
undowhen the history is empty should have no effect. - Calling
redowhen the history is exhausted (at the end) should have no effect. - The history index should not go below 0 or exceed the history length.
- Calling
Expected Behavior:
The hook should behave similarly to useState but with the added functionality of undo/redo. The component using the hook should re-render whenever the state changes, is undone, or is redone.
Examples
Example 1:
Input:
Initial state: "Hello"
Update function called with "World":
Output:
Current state: "World"
History: ["Hello", "World"]
History index: 1
Undo function called:
Output:
Current state: "Hello"
History: ["Hello", "World"]
History index: 0
Redo function called:
Output:
Current state: "World"
History: ["Hello", "World"]
History index: 1
Example 2:
Input:
Initial state: 0
History limit: 3
Update function called with 1:
Update function called with 2:
Update function called with 3:
Update function called with 4:
Output:
Current state: 4
History: [0, 1, 2]
History index: 2
Example 3: (Edge Case - Undo beyond the beginning)
Input:
Initial state: "A"
History limit: 2
Update function called with "B":
Undo function called:
Undo function called:
Output:
Current state: "A"
History: ["A", "B"]
History index: 0
Constraints
- History Limit: The history limit should be configurable and defaults to 5.
- Input Type: The initial state can be of any type.
- Performance: The hook should be performant, avoiding unnecessary re-renders or complex operations. Consider the cost of copying state objects.
- Immutability: Ensure that state updates are handled immutably to prevent unexpected side effects.
Notes
- Consider using the spread operator (
...) orObject.assign()to create copies of state objects when updating the history. - Think about how to efficiently manage the history array to avoid excessive memory usage.
- The
undoandredofunctions should not directly modify the history array; they should update the current state and history index. - The history index should be carefully managed to prevent out-of-bounds errors.
- This is a good opportunity to practice using
useState, useRef, and functional updates in React.