atlassian / react-sweet-state

Shared state management solution for React
https://atlassian.github.io/react-sweet-state/
MIT License
871 stars 55 forks source link

Time travel and Undo/Redo #138

Closed IanDuncanson closed 2 years ago

IanDuncanson commented 3 years ago

Would it be possible to apply time travel type middleware to achieve Undo/Redo type functionality to a react-sweet-state store, including nesting? Similar in concept to redux-undo

We are building a type of "no-code" designer as well as a runtime and would like to use sweet-state for the runtime, but the designer needs undo/redo type functions as well, so just trying to see whether we can use sweet-state for both.

albertogasparin commented 3 years ago

Hey, sorry for the late reply. I don't see why not. It depends on the level of flexibility you are looking for, but the dumbest version is probably just storing the prev state values somewhere and re-setting them with a custom action (not tested):

const historyMw = new Map();
const history = ({ setState }) => (next) => (arg) => {
  // use setState as key
  if (!history.get(setState)) history.set(setState, { index: -1, states: [] });
  const historyStore = history.get(setState);

  if (historyStore.states.includes(arg)) {
    // undo-redo action
    historyStore.index = historyStore.states.indexOf(arg)
    return arg;
  }
  const result = next(arg);
  historyStore.index += 1;
  // remove future states on state change
  historyStore.states.length = historyStore.index;
  historyStore.states.push(result);
  return result;
};

defaults.middlewares.add(historyMw);

const historyUndo = () => ({ setState }) => {
  const historyStore = history.get(setState);
  const prevState = historyStore.states[historyStore.index - 1];
  if (prevState) setState(prevState);
}

This should collect unlimited states from all stores. You might want to whitelist and limit the history length. It should be even doable to put index in store state, to use it in the ui. Or it might be even better to do it per store by creating an action wrapper that overrides getState/setState to push/pop history on all/some actions.

const actions = {
  increment = (n) => undoable(({ getState, setState }) => { ... })
  undo: undoable.undo,
}
IanDuncanson commented 3 years ago

Thanks Alberto, and congratulations on react-sweet-state - great library!!!