With Immutable data structures going back to previous state is constant time operation.
From what I've seen currently with mobx it would involve deserializing previously serialized whole store, which is linear with size of the store.
Example use case in which it would be useful would be consistent optimistic updates for actions needing confirmation from the server. as previeved by Lee Byron
I was thinking if it is possible to make use of mobx monitoring every mutation to then just simply roll back all the changes. This gives us rolling back single mutation in constant time.
Naive implementation:
https://gist.github.com/asterius1/551769caa740bab786e4d9c772a925c9
I guess it would need some mobx.observeAll(listener) listening to every change of observable enities. With that we can just back up all the changes and apply them in reverse to undo action.
For example: action A triggered changes 3, 4, 5, and action B triggered changes 6, 7. To undo just action A we could undo actions from 7 to 1, and than apply changes of action B, or with flux-like architecture, just apply B again.
There could be some edge cases with usages of mobx.observe or having complex asynchronous side effects.
Do you think it is feasible to do in mobx or will it be to hard/impossible?
I have undo/redo working here pretty seamlessly: https://github.com/AriaFallah/mobx-store/blob/master/src/index.js
Let me know if you want an explanation of how it works. It's basically what you proposed here.
The only issue with it is explained here: https://github.com/mobxjs/mobx/issues/258
Hmm, that's interesting.
How well does mobx-store work with not normalized or deeply nested data structures? Main reason I would use mobx is possibility of having my store composed of classes referencing each other directly.
@AriaFallah thanks for sharing!
@asterius1 you are right, undo / redo or time travelling is really trivial with immutable data. (well at least time travelling, undo / redo as a end-user feature is usually a lot more involved and requires transactions, undo / redo-ing side effects etc, so let's for the sake of this thread focus on the simpler time travelling).
To achieve time travelling you can serialize the whole state indeed, but that is a really naive approach. In fact, you only need to snapshot _changed_ data. And that can be achieved quite easily in MobX because that is reactive process. By snapshotting and concatenating state you can still time travel with constant time and space overhead. You can try that yourself in the ReactiveConf demo. It uses createTransformer to memoize the serialized result for unchanged objects. The whole concept is explained here (under "Tracking mutable state...")
Your proposed approach, to record mutations, should also be very feasible because MobX 2.2 introduces spy(callback) which is indeed like a global observeAll listener. You can already use [email protected] to try it out! It should be pretty straight forward I think to implement time traveling with this approach, and the advantage over snapshotting is that it is a generic solution. So feel free to play with that!
@asterius1
I'm not quite sure what you mean, but if you want to ask your question more specifically, you can create an issue over at my repo. If what you want doesn't exist, I can make it part of mobx-store 😄
@mweststrate
I'm not quite sure if you're familiar with what I'm doing for undo/redo, but the simple idea is that currently I'm calling observe on everything added to the store and undo/redoing the change event that mobx emits when a mutation occurs.
Could this be done better with spy? Also thoughts on https://github.com/mobxjs/mobx/issues/258?
@AriaFallah yep I look at it (a while ago thouhg). But I think in your case it easier because you don't have "deep" structures. Or am I mistaken now?
spy would not be nice in your case I think, because that would report all global changes while your lib now can have multiple mobx-stores, which is really nice.
I think #258 is a good idea, but first I'm gonna wrap up 2.2 ;-)
@mweststrate Just tried out spy, and it awesome :clap:
Having impression that if there is anything lacking in mobx it is being implemented or will be very soon. Very impressed :smile:
Most helpful comment
@AriaFallah thanks for sharing!
@asterius1 you are right, undo / redo or time travelling is really trivial with immutable data. (well at least time travelling, undo / redo as a end-user feature is usually a lot more involved and requires transactions, undo / redo-ing side effects etc, so let's for the sake of this thread focus on the simpler time travelling).
To achieve time travelling you can serialize the whole state indeed, but that is a really naive approach. In fact, you only need to snapshot _changed_ data. And that can be achieved quite easily in MobX because that is reactive process. By snapshotting and concatenating state you can still time travel with constant time and space overhead. You can try that yourself in the ReactiveConf demo. It uses
createTransformerto memoize the serialized result for unchanged objects. The whole concept is explained here (under "Tracking mutable state...")Your proposed approach, to record mutations, should also be very feasible because MobX 2.2 introduces
spy(callback)which is indeed like a globalobserveAlllistener. You can already use[email protected]to try it out! It should be pretty straight forward I think to implement time traveling with this approach, and the advantage over snapshotting is that it is a generic solution. So feel free to play with that!