Skip to content

Instantly share code, notes, and snippets.

@perjo927
Created September 8, 2020 14:50
Show Gist options
  • Save perjo927/193a8c407b8f26f279bc7c89517c5d58 to your computer and use it in GitHub Desktop.
Save perjo927/193a8c407b8f26f279bc7c89517c5d58 to your computer and use it in GitHub Desktop.
Undoable reducer
import { CONSTS } from "../actions/index.js";
const {
actions: { UNDO, REDO },
} = CONSTS;
/*
Read more at:
https://redux.js.org/recipes/implementing-undo-history
# Handling Undo
1. Remove the last element from the past.
2. Set the present to the element we removed in the previous step.
3. Insert the old present state at the beginning of the future.
# Handling Redo
1. Remove the first element from the future.
2. Set the present to the element we removed in the previous step.
3. Insert the old present state at the end of the past.
# Handling Other Actions
1. Insert the present at the end of the past.
2. Set the present to the new state after handling the action.
3. Clear the future.
*/
export const undo = ({ past, present, future }) => {
const previous = past[past.length - 1];
const newPast = past.slice(0, past.length - 1);
return {
past: newPast,
present: previous,
future: [present, ...future],
};
};
export const redo = ({ past, present, future }) => {
const [next] = future;
const newFuture = future.slice(1);
return {
past: [...past, present],
present: next,
future: newFuture,
};
};
export const defaultReduce = (state, reducer, action, areEqual) => {
const { past, present } = state;
const newPresent = reducer(present, action) || undefined;
return areEqual(present, newPresent)
? state
: {
past: [...past, present],
present: newPresent,
future: [],
};
};
export const undoReducerActionMap = new Map([
[UNDO, undo],
[REDO, redo],
]);
export const getUndoableReducer = (reducer, areEqual) => {
// Call the reducer with empty action to populate the initial state
const initialState = {
past: [],
present: reducer(undefined, {}),
future: [],
};
// Return a reducer that handles undo and redo
return (state = initialState, action = { value: null, type: null }) => {
const defaultReducerFunction = defaultReduce;
const reducerFunction = undoReducerActionMap.get(action.type);
return reducerFunction
? reducerFunction(state)
: defaultReducerFunction(state, reducer, action, areEqual);
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment