Implementing Undo/Redo with Redux
Code example illustrating the implementation of undo and redo functionality for actions in Redux using the new immerReducer utility.
// An action type for undoing an action
const UNDO_ACTION = 'UNDO_ACTION';
// An action type for redoing an action
const REDO_ACTION = 'REDO_ACTION';
// Implementing the reducer with undo/redo using the immerReducer utility
function undoable(reducer) {
const initialState = {
past: [],
present: reducer(undefined, {}),
future: []
};
return function(state = initialState, action) {
const { past, present, future } = state;
switch (action.type) {
case UNDO_ACTION:
if (past.length === 0) return state;
const previous = past[past.length - 1];
const newPast = past.slice(0, past.length - 1);
return {
past: newPast,
present: previous,
future: [present, ...future]
};
case REDO_ACTION:
if (future.length === 0) return state;
const next = future[0];
const newFuture = future.slice(1);
return {
past: [...past, present],
present: next,
future: newFuture
};
default:
// Delegate handling the action to the passed reducer
const newPresent = reducer(present, action);
if (present === newPresent) {
return state;
}
return {
past: [...past, present],
present: newPresent,
future: []
};
}
};
}
// Usage in a Redux createStore call
// createStore(undoable(yourReducer));
This code snippet defines an 'undoable' higher-order reducer, which wraps a user's reducer and provides undo/redo functionality. It maintains past, present, and future state slices and handles UNDO_ACTION and REDO_ACTION types to shift the present state into the past or the future respectively, while ensuring the reducer's immutability by not directly modifying any part of the state. Default actions are passed to the original reducer, and when the state changes, the resulting state is stored as a new 'present' with the old 'present' moved to the 'past'.