holden
holden•2y ago

Any way to undo/redo mutations?

Has anyone found a good way to implement undo/redo in a Convex app? I was previously using Liveblocks storage to store document state, and migrated to Convex. Overall happy with Convex so far, but the one Liveblocks feature I'm sad to lose is the useUndo, useRedo, useCanUndo, useCanRedo hooks: https://liveblocks.io/docs/api-reference/liveblocks-react#useUndo Those made it really easy to add undo/redo buttons to my app, and have it work across tabs/users. It doesn't look like Convex has anything built in for this? Anyone know if there's a good 3p library that might help here? This blog post convinced me I probably don't want to implement it myself 😉 https://liveblocks.io/blog/how-to-build-undo-redo-in-a-multiplayer-environment
10 Replies
sujayakar
sujayakar•2y ago
yeah, we haven’t implemented this in a convex helper or template yet. would love to hear if anyone else in the community has done something similar in a convex app! one thing that might be a bit easier on convex w/strong consistency guarantees is to have application updates in an append only journal table, sorted by creation time, where each update has a user ID. then, when deciding to undo, we can look at the previous entries for the current user and then synthesize them into a new journal entry, potentially undoing multiple entries as they mention in the article. but yeah, definitely not easy, and i can also see the value of having the framework help out here!
magicseth
magicseth•2y ago
Here's how we're handling it (roughly) at https://hellowonder.ai. It isn't something that actually needs to be exposed to the client, as all of your convex functions can essentially work the same. Imagine you have a table called game_states, you add a new table called current_game_state that just has one field Id<"game_states"> You use current_game_state Ids everywhere you want a game_state. When you have a mutation, you merely create a new game_state, and update the current_game_state to point the new state. This only really possible BECAUSE of the guarantees Convex makes around atomicity. Now, to implement undos, you keep a back reference from game_state to the canonical current_game_state object that referenced it, and you sort them by creation date and move backwards. If you add a field that indicates which operation was performed, you can provide nice labels for your undos, or instantly jump back to any state. If you name your tables "games" and "game_states" your client code can conveniently use Id<"games"> everywhere and never even know that there are many copies in the db.
David Alonso
David Alonso•7mo ago
We're building https://fireview.dev and this would be extremely useful for us but implementing it manually feels like a huge lift, are there any plans to make this part of the framework @sujayakar ?
Fireview - The Firestore Console You Deserve
Fireview helps your team manage and visualize your Firestore data with ease.
David Alonso
David Alonso•7mo ago
the api that Liveblocks provides seems super intuitive to me
ampp
ampp•7mo ago
I don't think its that hard, i wrote a event management system to handle arrays of mutations or scheduled actions and various combinations. w/logging debugging etc, i thought it would be a lot harder, but when you just get into it its not so bad imho. I am curious what a convex helper for this would look like. For my needs all states need to be saved, so it would be more of a jump back. I'm planning to just run things in reverse.
David Alonso
David Alonso•7mo ago
hmm okay, I think it would just be very helpful to be able to store mutation ids in some journal table and having a Convex helper that can reverse mutations by some kind of id.
sujayakar
sujayakar•7mo ago
yeah, we still haven't designed what a good convex-helpers library would look like for undo. if you end up implementing it yourself, I'd be curious what patterns emerge.
David Alonso
David Alonso•7mo ago
I was just quickly skimming through the start of this blog post: https://stack.convex.dev/how-convex-works and realized that the transaction log already stores the deltas that come in handy for undos and redos. Just being able to filter transactions by users who authored them and then applying inverse transactions (for undo) could already be very useful. I don't know much about the convex internals so I apologize if this makes no sense hahah
Stack
How Convex Works
The full, unabridged story on how the Convex internals work.
David Alonso
David Alonso•7mo ago
there's currently no way to access this transaction log right?
sujayakar
sujayakar•7mo ago
that question makes a lot of sense! and yes, we currently don’t expose the transaction log. we likely will in the future, but i suspect it still won’t be a perfect fit for building undo/redo in an application. one issue, as you mention, is that the log contains entries for all tables, so there won’t likely be a way to efficiently filter for transactions that are for a particular user within a particular table.

Did you find this page helpful?