use-undo-redo
The useUndoRedo hook provides a simple and efficient way to manage undo and redo functionality in your React applications. It helps you maintain a history of state changes, allowing users to seamlessly navigate through different states of your application.
Compatibility
React 18.2 or later, including React 19 ("react": ">=18.2.0 <20").
Demo
Sponsor
Sponsored by JSpark
Features
- Undo and Redo: Effortlessly navigate backward and forward through the state history.
- Configurable History Capacity: Define the maximum number of state changes to keep in memory.
- Debounce Support: Optimize performance by controlling the frequency of state history updates.
- History Management: Access the full history stack, reset the history, or jump to a specific point.
- Lightweight and Easy to Integrate: Minimal setup required to integrate into existing React components.
Installation
npm i @anandarizki/use-undo-redo
Basic Usage
//import the hook
import { useUndoRedo } from "@anandarizki/use-undo-redo";
//your state handler
const [state, setState] = useState();
//initialize hooks and pass your state and setState as the argument
const [undo, redo] = useUndoRedo([state, setState]);
Integrating into an Existing Component
You don't need to overhaul your existing state management. This hook can be seamlessly integrated into your code with just a single line addition.
Original component
import { useState } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
return (
<div>
<div>
<button onClick={() => setCount(count - 1)}>-</button>
{count}
<button onClick={() => setCount(count + 1)}>+</button>
</div>
</div>
);
}
Component with useUndoRedo
import { useState } from "react";
import { useUndoRedo } from "@anandarizki/use-undo-redo";
function MyComponent() {
const [count, setCount] = useState(0);
//new line
const [undo, redo] = useUndoRedo([count, setCount]);
return (
<div>
<div>
<button onClick={() => setCount(count - 1)}>-</button>
{count}
<button onClick={() => setCount(count + 1)}>+</button>
</div>
<div>
<button onClick={undo}>
Undo
</button>
<button onClick={redo}>
Redo
</button>
</div>
</div>
);
}
useUndoRedo
useUndoRedo<T>(primaryState: [T, (v: T) => void], options?: Options<T>): Output<T>
-
primaryState: An array containing the state value and the state setter function. This allows the hook to manage the history of this specific state.
-
options:
- capacity (optional): The maximum number of entries kept in history, including the starting state, so
capacity: 10allows 9 undo steps. Values below 1 are treated as 1, andInfinitykeeps everything. Default is 10. - debounce (optional): The time in milliseconds to debounce history updates. It is useful when your state updates frequently, i.e. when updating state using a controlled input. Calling
undoorjumpTowhile an update is pending records it first, so it can be redone. Default is 0 (no debounce). - clone (optional): How a state value is copied into history.
truedeep-clones it with lodashcloneDeep;falsestores the value as-is, which is faster and uses less memory but is only safe when you never mutate state in place; a function(value) => copylets you provide your own copy. Default istrue.
- capacity (optional): The maximum number of entries kept in history, including the starting state, so
-
Returns: An array containing:
- undo: A function to revert to the previous state.
- redo: A function to move forward to the next state.
- An object with the following properties:
- canUndo: A boolean indicating if an undo operation is possible.
- canRedo: A boolean indicating if a redo operation is possible.
- reset: A function to clear the history and reset the pointer.
- history: An array of
{ value, timestamp }entries representing the current state history. Treat it as read-only. - pointer: The index of the current entry in
history. - jumpTo: A function to jump to a specific point in the history by index. Invalid indexes are ignored.
undo, redo, jumpTo and reset keep the same identity across renders, so they are safe to pass to memoized components or use as effect dependencies.
The Options, Output and StateHistory types are exported for TypeScript users.
Notes
- Changes are detected with a deep equality check (lodash
isEqual). Setting a new object with the same content as the current entry is not recorded as a change. - Class instances with private (
#field) properties can't be compared or cloned by lodash; store plain data instead.
Full usage
const [undo, redo, { canUndo, canRedo, jumpTo, history, pointer, reset }] =
useUndoRedo([state, setState], {
debounce: 500,
capacity: 20,
});