Debounce calls to a mutation without doing so for the optimistic update
Say you have a text field in a Notion-like application (e.g. Fireview in screenshot) where we use optimistic updates to trigger global changes on every keystroke.
In the component we use a mutation like this:
We want the optimistic update to be triggered on every keystroke but it would be great to debounce server mutation calls to save some costs, I just have no idea how to do this...

8 Replies
hey @David Alonso, @ian's been working on collab text editing on convex (https://github.com/get-convex/prosemirror-sync). if that'd be a drop in for the functionality you want, the two of you should chat. the text editing stuff is a bit early but will work.
if you'd like to debounce mutations yourself (rather than using a text editor component), I have a pattern from https://github.com/sujayakar/tldraw-convex that uses single flighted mutations and has immediate optimistic updates. lmk and I can go into more detail for how it works.
GitHub
GitHub - sujayakar/tldraw-convex
Contribute to sujayakar/tldraw-convex development by creating an account on GitHub.
this part maybe suggests I might have to wait, but Ian can probably confirm

Would love to see a bit more here: https://github.com/get-convex/prosemirror-sync/blob/main/example/convex/example.ts, probably in the works...
Where can i find the pattern in your repo?
here's the state manager: https://github.com/sujayakar/tldraw-convex/blob/main/src/multiplayer/ConvexRoomManager.ts
the core idea is to track an "operation" through a few phases:
1. it's submitted by the user and should show up immediately in the UI
2. it gets batched up into a mutation and sent to the server
3. it gets applied on the server and reflected in all queries.
GitHub
tldraw-convex/src/multiplayer/ConvexRoomManager.ts at main · sujaya...
Contribute to sujayakar/tldraw-convex development by creating an account on GitHub.
the main trick is to be able to know when a mutation goes from (2) to (3).
we do that by (ab)using regular optimistic updates. the data fetching query (https://github.com/sujayakar/tldraw-convex/blob/main/convex/getRoom.ts) returns its usual stuff + a
null
mutation ID, and then the optimistic update for (2) sets that field in the local store.
let me know if that code makes sense + i'm happy to code review anything on your side! we haven't packaged this up into something reusable yet, but it's on our radar for v2 of our optimistic updates API.GitHub
tldraw-convex/convex/getRoom.ts at main · sujayakar/tldraw-convex
Contribute to sujayakar/tldraw-convex development by creating an account on GitHub.
will take a look! thanks for sharing 🙏
The current example reflects all changes locally immediately and single-flights the updates. So there isn't one mutation per keystroke, the updates are batched. At fastest it will be in a constant loop of rebasing & submitting changes since the last submission. The comment on configurable debouncing means:
- The first keystroke when the system is idle will trigger the first update, vs. always waiting at least a little time for multiple changes to accumulate.
- It doesn't have configuration for how often snapshots are submitted. I could see usecases for (a) on every submission, (b) every N operations, (c) after editing has died down - maybe N seconds after the latest keystroke, or (d) dynamic based on the size of the document and size of deltas (automerge chooses to compact when the binary size of the deltas exceeds the binary size of the last compaction, so it writes bigger documents less frequently)
You can see my single flighting logic here, and an old article on implementing single flighting here (though a Discord user made a much simplified version here I haven't verified but seems right)
I can't ask for better answers, you guys rock! 🤘