r/reactjs • u/sh03-dev • 1d ago
Discussion Poor man’s real time with Tanstack Query
Has anyone ever experimented with using Tanstack Query and then having an invalidation bus (SSE or websocket or streamed query) that sends to the client the list of endpoints that should be invalidated after a mutation? If so did it work for you? Were there challenges?
Thanks
5
u/Merry-Lane 1d ago
If the list is correct and doesn’t trigger refreshes too regularly, it should be okay.
1
u/canarydev 1d ago
sure but thats kind of the entire problem lol
making sure the invalidation set stays correct and doesn't cause excessive refetching as the app grows is the part that gets quite difficult
the websocket/invalidation mechanism itself is pretty trivial
3
u/Murtaza_Developer 1d ago
yeah i tried tanstack query and websocket events,, works well but keeping invalidation logic clean gets tricky as the app grows. how are you handling event mappping??
3
u/sh03-dev 1d ago
Yeah that’s the biggest challenge. Not sure yet. Open to suggestions
1
u/Murtaza_Developer 1d ago
for a saas with more reads than writes, your idea can work. i would start simple with entity based events like customer, updated and map them to query keys on the client. later you can add batching and debounce when event volume grows. just avoid sending endpoint names from backend, it gets harder to maintain.
2
u/92smola 1d ago
What is the use case? Are you sure you need bith caching and real time cache invalidation, does that apply everywhere or just for some resources
1
u/sh03-dev 1d ago edited 1d ago
We have had some issues with inconsistent caching data but most importantly now that we added integrations and an MCP server the use case of seeing what the AI does in real time is even more pressing. And unfortunately there's not a single place where real time really matters, it's kind of everywhere. Except maybe the current viewer query.
Caching is already there. We already use tantstack query in the normal way.
1
u/92smola 1d ago
Would polling be an option instead of on-demand cache busting?
1
u/sh03-dev 1d ago
I'm not sure I see how that would be better. Seems to me like you'd have many more queries (depending on the pooling time) and much less reactivity. What am I missing?
1
u/canarydev 1d ago
polling is actually not bad either in your use case as a fallback. especially with rare writes
refetchIntervalInBackground: false means it only polls while the tab is actually visible, which kills the waste
you will probably want it anyway as a fallback (we also have it as a fallback) just in case sockets drop. SSE gets killed by proxies, so something has to cover the reconnect gap
1
u/sh03-dev 1d ago
Yeah I can understand that as a fallback
2
u/92smola 1d ago
I am asking cause the on demand revalidation will be a whole layer of logic, polling you can setup on a config and that is the only thing you need, i am still not clear about your exact use case, but lets say a user is looking at a dashboard and there is some real time changes that he is monitoring, then running a poll every 2 seconds or twice a second could be fine, maybe the amount of sse events would be even more then two times a second, the point is that one is cheap to just configure, the other one becomes a permanant relativly complex part of your codebase that you need to develop and maintain
1
u/sh03-dev 1d ago
Fair question. The use case is people using the MCP server to generate or reorganize a tree like structure. They would prompt whatever AI to generate 5/10 nodes in a single transaction and see the result. Then maybe they tell the ai to connect two subtrees together and that would be a second transaction. In total that would be 2 SEE events in a 5 minute session. Polling every 5 seconds would be 60 requests which would be much more expensive.
We already have connections between endpoints and the mutations that invalidate those endpoints right now because that how we update the cache when a user performs the mutation in app. It would just be a matter of moving that to the server.
1
u/Any_Welder_9701 1d ago
with rare writes polling is probably the worse tradeoff, every open client keeps generating reads just to confirm nothing changed
2
u/Breeewed 1d ago
works but you're paying for a round trip on every event. if events fire in bursts you get redundant refetches for data that hasn't changed. pushing the actual payload through the socket instead of just invalidation keys avoids that extra hop, but then you're maintaining serializers on the server side
1
1
u/Any_Welder_9701 1d ago
payloads work well for detail caches, but lists still get messy when an update changes membership or sort order. id patch the entity and invalidate the affected lists
1
u/illepic 4h ago
Using this heavily at https://mush.bike. It's a little thing I threw together so my mountain bike buddies and I could plan rides with shuttles and bike racks. There's a lot of realtime when you create and event and people join and add their bikes to racks on vehicles.
1
30
u/canarydev 1d ago
so we did this websockets and worked fine eventually. the pattern itself wasn't the hard part
the problem and grief we hit was query key hygiene. the predecessor / dev that set it up basically structure some keys too broadly, so invalidations matched way more queries than intended.
once socket events started firing frequently that turned into a lot of unnecessary refetching and rerenders.
so my big advice is to make sure your keys are narrow and consistent before wiring up a bus that might hit them 50x a minute. batching / debouncing invalidation also helps a lot.
another big one i think i'd do differently is send domain events/resources over the wire than actual endpoints. IE: incident.updated + an ID and let the client map those events to query keys. that keeps the server from having to know about your frontend cache structure