I also wanted to create a simple undo functionality, but had already shipped an app with redux-storage that serializes and loads the state for every user. So to keep it backwards-compatible, I couldn't use any solution that wraps my state keys, like redux-undo does with past: []
and present:
.
Looking for an alternative, Dan's tutorial inspired me to override combineReducers
. Now I have one part of the state: history
that saves up to 10 copies of the rest of the state and pops them on the UNDO
action. Here's the code, this might work for your case too:
function shouldSaveUndo(action){
const blacklist = ['@@INIT', 'REDUX_STORAGE_SAVE', 'REDUX_STORAGE_LOAD', 'UNDO'];
return !blacklist.includes(action.type);
}
function combineReducers(reducers){
return (state = {}, action) => {
if (action.type == "UNDO" && state.history.length > 0){
// Load previous state and pop the history
return {
...Object.keys(reducers).reduce((stateKeys, key) => {
stateKeys[key] = state.history[0][key];
return stateKeys;
}, {}),
history: state.history.slice(1)
}
} else {
// Save a new undo unless the action is blacklisted
const newHistory = shouldSaveUndo(action) ?
[{
...Object.keys(reducers).reduce((stateKeys, key) => {
stateKeys[key] = state[key];
return stateKeys;
}, {})
}] : undefined;
return {
// Calculate the next state
...Object.keys(reducers).reduce((stateKeys, key) => {
stateKeys[key] = reducers[key](state[key], action);
return stateKeys;
}, {}),
history: [
...(newHistory || []),
...(state.history || [])
].slice(0, 10)
};
}
};
}
export default combineReducers({
reducerOne,
reducerTwo,
reducerThree
});
For me, this works like a charm, it just doesn't look very pretty. I'd be happy for any feedback if this is a good / bad idea and why ;-)