holden
holden2y ago

Debouncing mutations?

hi! I'm using Convex to store/update the state of a "project" object in my app. My naive approach works fine (and feels very fast), except in one case where I have a slider that feels very laggy (I think because it's attempting to update state many times/sec during the drag). What's the best way to fix this? After reading your docs, I was considering adding the useSingleFlight hook to throttle my mutations, and use optimistic updates to keep my local state/app feeling fast. Does it sound right that I should do both of these, or is that overcomplicating? Ideally, I want my local state to update instantly so my app feels snappy, even if mutations to the server are throttled somehow. (just looking for a pointer that I'm on the right track before I go implement all of this - thanks!)
2 Replies
ballingt
ballingt2y ago
hi holden! I think using both of these is a good direction. Restating what you said, you want local optimistic update so the slider responds instantly. And you want to throttle because otherwise mutations stack up and get behind. I used ordinary throttling for changing text color in https://convex-clerk-users-table.vercel.app/, so no optimistic updates. And you can feel it! For the best feel I would not use the built-in Convex optimistic updates (which are a better fit for slower changes) and instead use local state that updates instantly. Do a throttled or single-flighted update separately, and don't rely on useQuery state here. The optimistic update in the React client are good for accounting for latency, but not faster updates than you actually want to report to the server. So my suggestion is not to use Convex optimistic updates, roll your own optimistic updates with useState etc. In the onChange handler for the slider, update the local value immediately and throttle your running of a mutation — this could be with manually with a timer or by wrapping the mutation with a single-flight wrapper While a mutation is in flight, trust local state. Then once local state is no longer changing and there's not a mutation in flight, trust useQuery state. By writing your own optimistic update system like this you're taking responsibility for building (a very tiny) sync engine. When you know the domain-specific rules for syncing state (in this case, that a local slider should always win) this is a good trade.
holden
holdenOP2y ago
Very helpful, thanks! Glad I asked before spending time building this. Will give your suggested approach a try! OK, after playing with this a while, I ended up adding a local zustand store to get/set my state and have that call the convex mutations. I didn't bother to throttle the mutations, because they don't seem to cause a problem. They catch up on their own after you stop dragging - now they just don't block the UI from updating. This wasn't too hard to do, but seems to go against your "Zen of convex" as I now have two versions of my state to keep in sync (perhaps error prone). But duplicating state in some way seems unavoidable if I want my local state to update faster than Convex can handle mutations. I wonder if your optimistic update feature or client libraries could just handle this by default? The mental model I want is for read/write operations to feel instant locally, and then you guys handle the details of syncing for me. As long as mutations aren't too frequent, everything seems to Just Work. Would be awesome if Convex could also handle fast mutations somehow in your own caching layer so I don't have to!

Did you find this page helpful?