Skip to content

Instantly share code, notes, and snippets.

@mCzolko
Created October 23, 2018 15:12
Show Gist options
  • Save mCzolko/a3acaf2cd3655767ca7a61e15d9a9ba0 to your computer and use it in GitHub Desktop.
Save mCzolko/a3acaf2cd3655767ca7a61e15d9a9ba0 to your computer and use it in GitHub Desktop.
Redux Immutable Undo with diffs instead of saving whole state.
import { Map, List } from 'immutable';
const diff = require('immutablediff');
const patch = require('immutablepatch');
export const ActionTypes = {
UNDO: 'undoable/UNDO',
REDO: 'undoable/REDO',
LOAD: 'undoable/LOAD',
CLEAR_PAST: 'undoable/CLEAR_PAST',
};
export const ActionCreators = {
undo() {
return { type: ActionTypes.UNDO };
},
redo() {
return { type: ActionTypes.REDO };
},
load() {
return { type: ActionTypes.LOAD };
},
clearPast() {
return { type: ActionTypes.CLEAR_PAST };
},
};
export default function undoable(reducer, maxHistorySize = 50) {
const initState = Map({
past: List(),
present: undefined,
future: List(),
});
return (state = initState, action) => {
const present = state.get('present');
switch (action.type) {
case ActionTypes.UNDO: {
const previous = state.get('past').last();
if (!previous) {
return state;
}
const newPresent = patch(present, previous);
return state.withMutations((mutatedState) =>
mutatedState
.update('past', (past) => past.pop())
.set('present', newPresent)
.update('future', (future) => future.push(diff(newPresent, present)))
);
}
case ActionTypes.REDO: {
const next = state.get('future').last();
if (!next) {
return state;
}
const newPresent = patch(present, next);
return state.withMutations((mutatedState) =>
mutatedState
.update('past', (past) => past.push(diff(newPresent, present)))
.set('present', newPresent)
.update('future', (future) => future.pop())
);
}
case ActionTypes.LOAD: {
return action.state;
}
case ActionTypes.CLEAR_PAST: {
// be extremely careful with this one
return state.set('past', List());
}
default: {
const newPresent = reducer(present, action);
const difference = diff(newPresent, present);
if (!difference.size) {
return state;
}
return state.withMutations((mutatedState) =>
mutatedState
.update('past', (past) => {
if (!present) {
return past;
}
const newPast = past.push(difference);
return newPast.size > maxHistorySize
? newPast.delete(0)
: newPast;
})
.set('present', newPresent)
.set('future', List())
);
}
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment