Any suggestions on how to implement undo / redo as an easy-peasy user?
I see some discussion in #124
Sure, here's a pretty gnarly, rough-around-the-edges (mostly) working solution I tried putting together for this. Breaks when you try to "undo" but you only have one forward state change in history (probably because the history object is initialized with present as null instead of taking an init object argument and using that).
https://codesandbox.io/s/interesting-microservice-50jeh?file=/src/store.ts
https://codesandbox.io/s/interesting-microservice-50jeh?file=/src/App.tsx
FWIW I just copied/adapted the article and history algorithm from the Redux recipe here, I believe it's the one used in the "redux-undo" library.
https://redux.js.org/recipes/implementing-undo-history
The gist of it is you make this history container type for your store state:
interface Store {
todos: Todos;
}
interface StateHistory<T> {
past: Array<T>;
present: T | null;
future: Array<T>;
}
let storeStateHistory: StateHistory<Store> = {
past: [],
present: null, // probably should't be "null", but whatever
future: []
};
And then sort of (ab)use a mixture of actionOn listeners and the fact that you still have access to dispatch(), so that you do this:
onChange: actionOn(
(actions) => [...Object.values(actions), "UNDO", "REDO"], // on every action, plus manual UNDO/REDO dispatch
And in onChange you switch on target.type, and:
UNDO/REDO
That's a helpful starter, thanks!
Some questions:
@mighdoll It's worth noting that you can attach redux based middleware as part of the store config. You could prob use that in place of actionOn in some form.
@ctrlplusb are the immer patches exposed at all? I didn't see anything in the docs about it but agree with @mighdoll being able to access those would be extremely useful.
OK, I leveled up enough to put together something for undo/redo. It was a bit trickier than expected to navigate to an undo/redo implementation that worked for computed fields, persistence, nested models, and whitelist/blacklisted properties.
To deal with computed fields, I patched easy-peasy to add an additional configuration option in createStore, tentatively named postActionReducer. It allows a user to add a reducer that runs after the normal easy-peasy actions are reduced, but before the computed getters are reattached to the resulting state. Perhaps there are other ways to make undo/redo work, but I thought it would be reasonable for easy-peasy to have an option like this. The key patch is:
const rootReducer = (state, action) => {
const stateAfterActions = reducerForActions(state, action);
const stateAfterCustom =
_customReducers.length > 0
? reducerForCustomReducers(stateAfterActions, action)
: stateAfterActions;
+ const next = postActionReducer
+ ? postActionReducer(stateAfterCustom, action)
+ : stateAfterCustom;
if (state !== next) {
_computedProperties.forEach(({ parentPath, bindComputedProperty }) => {
const prop = get(parentPath, next);
// hmm.. this is mutating state during the reduce.
if (prop != null) bindComputedProperty(prop);
});
}
return next;
};
What do you think?
I've published a library containing an undo/redo implementation for easy peasy apps. It's here: patched-undo-peasy.
It uses the state history suggested by @GavinRay97 and redux middleware, as suggested by @jchamb. Thanks for helping out.
The current implementation of undo/redo does not depend on the postActionReducer patch above. However, my app requires a patched version of easy peasy due to #539, so the implementation depends on patched peasy.
That is awesome @mighdoll
More incentive to fix #539 then.
Thanks for merging the #539 patch and publishing 3.4.0-beta.2.
I'll give it a try and republish undo-peasy without the patch.
Oops, my mistake. AFAICT 3.4.0-beta.2 doesn't include the #539 patch. If that's right @ctrlplusb, would you publish a -beta.3?
@mighdoll the v4 release is upcoming and will include this.
Please consider adding your work to the new Community Extensions page contained in the v4 docs.
Should be up by next week. 馃檹
Most helpful comment
I've published a library containing an undo/redo implementation for easy peasy apps. It's here: patched-undo-peasy.
It uses the state history suggested by @GavinRay97 and redux middleware, as suggested by @jchamb. Thanks for helping out.
The current implementation of undo/redo does not depend on the postActionReducer patch above. However, my app requires a patched version of easy peasy due to #539, so the implementation depends on patched peasy.