Does Redux have a built-in way to undo actions?

There's no built-in way to do this. but you can get inspired by how redux-dev-tools works (https://github.com/gaearon/redux-devtools). It basically have "time travel" functionality and it work by keep a track of all actions and reevaluating them each time. So you can navigate easily thorough all your changes.


Is there a built-in way in Redux to do this? Or would I have to write middleware for this?

Middleware sounds like the wrong idea in this case because this is purely state management concern. Instead you can write a function that takes a reducer and returns a reducer, “enhancing” it with action history tracking along the way.

I outlined this approach in this answer, and it's similar to how redux-undo works, except that instead of storing the state, you can store actions. (Depends on the tradeoffs you want to make, and whether it's important to be able to “cancel” actions in a different order than they happened.)


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 ;-)


I believe the idea is not so much "undo" as much as save a reference to the entire state tree each time an action passes through redux.

You would have a history stack made up of the application state at various times.

let history = [state1, state2, state3]

// some action happens

let history = [state1, state2, state3, state4]

// some action happens

let history = [state1, state2, state3, state4, state5]

// undo an action

let history = [state1, state2, state3, state4]

state = state4

To "undo" an action, you just replace the application state with one of the saved states.

This can be made efficient with data structures that support structural sharing, but in development we don't really need to consider resource constraints too much anyway.

Tags:

Redux