Created
October 23, 2018 15:12
-
-
Save mCzolko/a3acaf2cd3655767ca7a61e15d9a9ba0 to your computer and use it in GitHub Desktop.
Redux Immutable Undo with diffs instead of saving whole state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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