Web Dev Cody
Web Dev Cody14mo ago

Does convex deal with caching duplicate queries?

for example, let's say. I have a comments section on my app, and I need to look up the user's name based on the id for each comment. Will convex be performant if I did something like this
function UserName({userId}: {userId: string) {
const name = useQuery(api.users.getUserName, { userId}))
return name
}

function Page() {
return <div>
<UserName userId={'123'} />
<UserName userId={'123'} />
<UserName userId={'222'} />
<UserName userId={'222'} />
<UserName userId={'123'} />
<UserName userId={'222'} />
<UserName userId={'123'} />
</div>
}
function UserName({userId}: {userId: string) {
const name = useQuery(api.users.getUserName, { userId}))
return name
}

function Page() {
return <div>
<UserName userId={'123'} />
<UserName userId={'123'} />
<UserName userId={'222'} />
<UserName userId={'222'} />
<UserName userId={'123'} />
<UserName userId={'222'} />
<UserName userId={'123'} />
</div>
}
13 Replies
Web Dev Cody
Web Dev CodyOP14mo ago
or will that do a TON of requests for each UserName component
Web Dev Cody
Web Dev CodyOP14mo ago
I think this answers it if I assume this is talking about useClient and not caching on the backend side of things
No description
lee
lee14mo ago
Your example will only do one request, because useQuery is deduped on the client side. If you unmount all UserName components and then remount them, the client will do another request, and then the server-side caching will kick in, so it counts as another function call but doesn't count as database bandwidth and would have lower latency. Note also you don't have to worry about queries getting out of sync. If you have duplicate useQuery calls from one client (beneath a single ConvexProvider), they will update in a single react rerender.
Michal Srb
Michal Srb14mo ago
To clarify, it will do one request for each unique userId. If we compare using a single list query for all users vs a issuing one query for each user, there are some tradeoffs: 1. Less vs more function calls 2. The list query will update if any item changes, whereas individual queries will only update when given item changes. I think our vision is that one day you will be able to always use list queries and not have to worry about how updates propagate through it, but we're not there yet. @jamwt might have more thoughts here. Regardless of which approach you take, you might want to paginate the "outer" query which gives you a list of users, if there could be too many users in a given listing.
allen
allen14mo ago
I was curious why the choice was made to dump the client cache on unmount? It seems ideal to have immediate state available on remount, then update with server-side values if available. I use a similar pattern across my app, where, rather than "joining" on the server side, I just pass down FKs and query in names/avatars of authoring records. These identity attributes rarely change and rehydrating from local state would be preferable to them being null and needing a server fetch on remount, even if not hitting the db. Another instance this becomes less than ideal is in tab views, where my UI library unmounts panels on tab switching, when it may be common for users to go between tabs frequently. I could rework how tabs are mounted/unmounted, or hoist my query up to a Provider, but was interested in the rationale behind this design choice, as it seems preferable in several use cases to have data, albeit stale data, vs no data.
jamwt
jamwt14mo ago
as with many things in convex, it's opt-in and layered becuase you can augment to get it, but it's harder to take back (how much memory should we consider appropriate to use by default, etc) we hope to have better and better libraries for this over time, but we're also happy to just point people toward ones developed really well by the community if you all beat us to it
jamwt
jamwt14mo ago
Help, my app is overreacting!
Reactive backends like Convex make building live-updating apps a cinch, but default behavior might be too reactive for some use cases. Not to worry! L...
jamwt
jamwt14mo ago
why isn't a flexible, parameterizable version of this library already shipped? some kind of configurable "global" client side cache? either bundled or in npm? mostly time, to be honest. there's a lot of stuff we just haven't gotten to yet. there's a lot to build to fulfill the potential of convex. and anything "built in" has a really high bar for stability just b/c if it lands in core we take that commitment very seriously to "never" break it. so I'd expect to see things on stack, in npm, on github before convex builds in a version of this behavior. we'd want time to iterate on it with you all in projects and find out what works well in practice for most people
allen
allen14mo ago
Makes sense. Perhaps exposing some hooks into the query manager so middleware could be written? I’m hesitant to hack solutions to this atm because it usually involves creating less than ideal react component structures to adhere to convex’s behavior and to circumvent it rather than being able to tweak convex’s behavior at the source.
jamwt
jamwt14mo ago
yeah, it's a good call out that our libraires could be a bit more pluggable dunno if @ballingt has any thoughts
ballingt
ballingt14mo ago
One reason we haven't made stale data in particular the default is to support code that relies on data never being stale or out of sync, say if keys from one query result are used to index into another query result. We've looked at useQuery(q, args, {staySubscribed: 60000}) before but let people write this one themselves. These absolutely should be configurable, particularly behaviors that aren't local to the hook; you can craft a hook that stays subscribed to a query for longer with
const unsub = client.watchQuery(q, args).onUpdate(()=>{});
setTimeout(unsub, 5000);
const unsub = client.watchQuery(q, args).onUpdate(()=>{});
setTimeout(unsub, 5000);
but creating a global cache to store stale results is more involved, it makes sense to wrap the client or add it to existing clients. Letting developers opt into stale data makes sense, we'll just need to clearly communicate that once you use stale data you can no longer count on all queries in your app using results from the same timestamp. This is something HTTP-based state managers e.g. react-query just assume, so it's a very reasonable thing to provide! And Convex's powerful queries (compared to just subscribing to documents) make this style of authoring queries quite manageable. But it's a mental shift from just assuming all data is always up to date.
allen
allen14mo ago
To take it a bit further… I’m also interested in keeping that global cache in local storage or similar to rehydrate my entire app on load. Happy to go deeper with your team to share what I’m seeing around clientside caching in my app.
Michal Srb
Michal Srb14mo ago
@allen absolutely we are thinking about this caching too! Especially important on mobile.

Did you find this page helpful?