A generic, high-order reducer that allows you to transparently add undo/redo functionality on top of other redux reducers. redux-live-undo allows state to be updated as users type without creating undo steps for every state change.
Inspired by the omnidan's work on redux-undo, redux-live-undo provides a simpler interface with admittedly less options but more flexibility. Most importantly, redux-live-undo has the concept of "history checkpoints" that allow you to dispatch actions without making every change to the state a step in the undo history.
For example, this allows you live updating of UI state (such as in a text input) without creating an undo step for every single keypress.
redux-live-undo also allows you to track entire sections of your application state as a single history so undo/redo actions can be synced across various types of data.
redux-live-undo tracks a history of state of its sub-reducers. The state returned by redux-live-undo has this shape:
- present: the current state of the sub-reducers.
- past: an array of past state checkpoints, ordered from oldest to newest.
- future: an array of future state checkpoints, order from newest to oldest. Only populated after an undo or redo.
Note: New history checkpoints are only recorded when the action dispatched has the undoableHistoryCheckpoint
property set to true
and changeDetector
returns true.
Reducers can optionally provide a changeDetector
function to override what changes are allowed to be checkpointable.
Flags on actions can affect the behavior of the undo history state:
undoableHistoryCheckpoint
: Marks when the next state should be considered a checkpoint in the undo historyundoableIrreversibleCheckpoint
: Marks when the next state should clear any undo history. Typically used if related state in another system (eg. a backend service) cannot be reversed.
Given a simple reducer that tracks the state of a string:
const TEXT_UPDATE = 'TEXT_UPDATE';
function StringReducer(state = '', action = {}) {
switch (action.type) {
case TEXT_UPDATE:
return action.value;
default:
return state;
}
}
Use redux-live-undo to add reducer functionality:
import Undoable from 'redux-live-undo';
const rootReducer = Undoable({
string: StringReducer
});
When passed to combinedReducers, you will have access to present, past, and future states:
import { createStore } from 'redux';
const store = createStore(rootReducer);
store.getState();
// => {
// past: [''],
// present: {
// string: ''
// },
// future: []
// }
Dispatch an update as an input changes:
store.dispatch({
type: TEXT_UPDATE,
value: 'H'
});
store.getState();
// => {
// past: [''],
// present: {
// string: 'H'
// },
// future: []
// }
Dispatch an update with a checkpoint when you want a snapshot to jump back to, for example, when the input blurs:
store.dispatch({
type: TEXT_UPDATE,
value: 'Hi',
undoableHistoryCheckpoint: true
});
store.getState();
// => {
// past: ['', 'Hi'],
// present: {
// string: 'Hi'
// },
// future: []
// }
Undo to the last checkpoint:
import { UNDO } from 'redux-live-undo';
store.dispatch({
type: UNDO
});
store.getState();
// => {
// past: [''],
// present: {
// string: ''
// },
// future: ['Hi']
// }
Redo the action:
import { REDO } from 'redux-live-undo';
store.dispatch({
type: REDO
});
store.getState();
// => {
// past: ['', 'Hi'],
// present: {
// string: 'Hi'
// },
// future: []
// }
- @joshdover
- @tornstrom