Blog>
Snippets

Redux Undo/Redo with State Versioning

Provide an example of how to manage versions of the Redux state to enable undo and redo functionality, which can also assist with conflict resolution in multi-device environments.
const ActionTypes = {
    UNDO: 'UNDO',
    REDO: 'REDO',
    PERFORM_ACTION: 'PERFORM_ACTION'
};

// Action creators
const performAction = (action) => ({
    type: ActionTypes.PERFORM_ACTION,
    payload: action
});

const undo = () => ({
    type: ActionTypes.UNDO
});

const redo = () => ({
    type: ActionTypes.REDO
});
Defines Redux action types and creators for performing actions and undo/redo operations.
// Initial state for the undoable reducer
const initialState = {
    past: [],
    present: null,
    future: []
};
Sets up the initial state structure with past, present, and future arrays to track state versions.
// Helper functions to manage past, present, and future state
function updateState(state, action) {
    // ... Perform the state update logic ...
    return newState;
}

// Reducer that handles undo/redo and action performance
function undoable(reducer) {
    return function(state = initialState, action) {
        const { past, present, future } = state;
        
        switch (action.type) {
            case ActionTypes.UNDO:
                const previous = past[past.length - 1];
                const newPast = past.slice(0, past.length - 1);
                return {
                    past: newPast,
                    present: previous,
                    future: [present, ...future]
                };
            case ActionTypes.REDO:
                const next = future[0];
                const newFuture = future.slice(1);
                return {
                    past: [...past, present],
                    present: next,
                    future: newFuture
                };
            case ActionTypes.PERFORM_ACTION:
                const newPresent = updateState(present, action.payload);
                if (newPresent === present) {
                    return state;
                }
                return {
                    past: [...past, present],
                    present: newPresent,
                    future: []
                };
            default:
                return state;
        }
    };
}
This reducer wraps another reducer to add undo/redo functionality. It uses helper functions and updates state versions according to the action types UNDO and REDO.
// Example of using the undoable reducer with a simple counter reducer
function counterReducer(state = 0, action) {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

const undoableCounterReducer = undoable(counterReducer);
An example of how to wrap a simple counter reducer with the undoable function to make it support undo/redo actions.
// Dispatching actions to the store
store.dispatch(performAction({ type: 'INCREMENT' }));
store.dispatch(performAction({ type: 'INCREMENT' }));
store.dispatch(undo());
store.dispatch(redo());
Demonstrates dispatching increment actions and then undoing and redoing an action in the store.