Hi I m working on a Next js app with
Hi! I'm working on a Next js app with Convex. I have a few questions 😄
1. Is it normal that every time I make a query/mutation, it does it twice at a time?
2. How does caching work? I read that Convex handles caching automatically, but I don't know how/where that works. For context, I am querying some data on the first page. When I navigate to another page, then go back to the first page, it queries the data again. I'd expect it to save the data from the previous query. Sorry, I'm sort of new to caching.
I appreciate any insights. Thanks!
70 Replies
hi @Edwin ! thanks for using convex.
it definitely should not be doing queries and mutations twice. can you share a bit on (1) how you're observing them happening twice (on the logs page of the dashboard? from console.log statements?) and (2) how you're calling into these convex functions? are you using the
useQuery
and useMutation
react hooks?
caching works at the function level on the server, not the client. so, after a navigation, you could see the client issue the query to the server again, but then the server will respond much more quickly the second time. it's pretty similar to how CDNs cache static assets.Hi! Thanks for the quick reply.
1. I've observed the duplicate queries and mutations both on the dashboard logs page and console.log statements.
2. Yes, I'm using the
useQuery
and useMutation
react hooks
I'll provide a screenshot below of one of the instances, wherein I actually followed the guide on authentication with Auth0
As you can see from the dashboard logs, it logs it twice


cool, are you using next? and the behavior here is that you have a page with that context, and it's calling
user/storeUser
twice on page load?yup to both
same goes with my query on a page
can you add a
console.log
right before the await storeUser()
and see if it prints twice in the browser and/or your next dev
console output?
one hypothesis is that this could be next rendering it multiple times -- once on the "server" (i.e. your local node server) and once in the browser. I don't think this is it (useEffect
shouldn't be running in SSR, right?) but seeing it issue the mutation twice on the client would be a good lead.
it logs twice as well
interesting. I wonder if the
UserContext
is getting mounted, unmounted, and then remounted for some reason. (and the same for the component that calls useQuery
.)
one hypothesis: are you setting up the UserContext
in your _app.tsx
file? if it's in the page, then it could get destroyed and recreated on page navigations.yeah, the
UserContext
is set up on _app.js
. is it supposed to be somewhere else?
or is perhaps the hierarchy wrong?
nope, that looks right!
@Edwin, can we try minimizing this issue to just the query? if we find out why that's running twice, perhaps that'll explain the
UserContext
problem too. can you screenshot the component that calls useQuery
? and I'm curious about the component hierarchy between the page and that component.sure thing
where i call the query is actually the index page, not a component. initially, i had used the data from the
useQuery
(the getIntro
) directly, but i tried using a state to test the caching. either way, the query is still called twice on page load
if you hover over the log entry in the dashboard's log view, by clicking on the double arrows, you can expand into a detail view, which will show the arguments. curious if the
user
passed to user/getIntro
is the same on the second invocation.
but overall I'm pretty puzzled 🤔
given the console.log
within the useEffect
above, it has to be that at least the context and below is getting unmounted and remounted somehow, yeah?yeah, they seem to be the same


i couldn't say for sure. if it is getting unmounted and remounted, shouldn't i notice it with the loading indicator? but the loading indicator is smooth
could it be an issue with convex? i am using js for my pages and components, and ts for convex functions. i dont know if that has anything to do with it
it could be! but ts on the server and js on the client should be totally fine.
the
console.log
we saw above tells me that we're mounting the context multiple times, which definitely seems suspicious -- we're initiating two calls to await storeUser()
on a single page load. but all the code so far looks reasonable.two tabs open with the app? while you guys get cleverer I'll get simpler just in case and ask the dumb questions 😄
hold on, you may be right. let me check my 100+ tabs open for a sec
nope, i only have one tab with the app
I guess that makes sense if you're seeing the duplicate calls within the local JS console as well...
yeah, this is puzzling. not sure if we've seen this one before
@Edwin, do you have the react devtools installed? https://beta.reactjs.org/learn/react-developer-tools
React Developer Tools
A JavaScript library for building user interfaces
yup, i do
I haven't used them a ton, but I think the profile option can help diagnose what's causing rerenders.
oh sweet
i'll look into it for a bit
cool, let us know what you find! sorry your initial experience with convex has been rocky -- once we figure this out we'll see if there's a way to smooth out this sharp corner.
it's getting late for me so I'm gonna sign off until tomorrow. if you're comfortable sharing your repo, I'd be happy to take a look. or we could hop on a call to pair debug too.
Hi I'm available to continue debugging if you'd like (west coast time 😆 )
thanks a lot for the help. i appreciate it! hats off to you guys for creating convex, i'm liking it so far. i may be off to a rocky start but i'm up for the challenge 😄
Hi! I've tested out the profiler on react devtools, would you want me to export it and send it here?
not sure if im understanding it right, but it does seem the
UserContext
is getting rendered twiceIf you could export or screenshot the rerender, that would be awesome, thanks! I'll try profiling our test auth0 app to compare
i believe you can load the profile on react devtools. let me know if it doesn't work
Heads up that React double-renders in dev mode — sounds just a bit like this! If you turn off React Strict mode you'll hopefully see this behavior stop.
(I'm not caught up on this thread, let me catch up)
ohh, interesting. i don't think i am using strict mode though. i don't remember enabling it, but i'll check again
In Next there's a <Strict> wrapper sometimes
There also seems to be
reactStrictMode
as an option in next.config.js
(also I would've thought it wouldn't double print to the console on the re-render but maybe I'm wrong)found it. it was enabled here
disabling it doesn't make a difference, though. queries and mutations are still duplicated
wait hold on, maybe i have to restart the server
Oops, sorry for the goose chase!
there we go. yup, that fixes it
thanks for the help, guys!
and just to follow up on my second original question about caching, since convex caching works at the function level on the server, not the client, i'll have to manage the client caching manually, right?
If you can raise that useQuery up to a component that remains rendered between navigations then it will remain loaded
I see, that makes sense. Thanks a lot!
If there's some different behavior that would be useful to you it'd be great to hear! The useQuery behavior works well for a lot of things, but there's potential to e.g. integrate with things like Redux or Jotai if that's your speed
Thanks for the heads up! Jotai seems promising, I'll have a deeper look into it. I'll definitely let you know if I come across anything in the future. 🙌
We recommend using Convex without these, but integrating with an existing codebase that could be useful
Our goal is that Convex is enough of a state manager itself along with React to provide the behavior you need. If there's not a convenient way to raise that useQuery up (btw you can use the same useQuery() call in multiple places) then it's helpful to hear about it
Awesome! Yeah let us know, it's always fun to hear from folks.
no question too small!
Hmm yeah, the thing is, the useQuery is called at a page, not a component. I want to conditionally render components on the page depending on the data returned from the hook. Since the data doesn't change very often, I don't want to have to wait for the query every time I navigate to the page. So I'm looking for a way to cache it and use that to render my components, and only update the cache if it doesn't match with the database
gotcha, is this useQuery("user/getIntro", user)? What do you think of adding that to Journeyer in _app, maybe in UserContext?
(very interested in feedback here, there are def other ways this could work)
Yeah, that's also an option I am considering. Though the data would only be accessed from one page (index.js), so it feels unecessary to store it in a context. Also, how would that be practically different from using something like Jotai or Redux?
It doesn't need to be stored there or prop-drilled, just adding
useQuery("user/getIntro", user)
is enough
The philosophy here (again, feedback wanted) is declaring the data dependencies of components, and always knowing exactly when they are or are not cached
Instead of caching, which could potentially get out of date, useQuery subscribes to the getIntro query
So if the data does change, or if you push new backend functions, this query will rerun automatically. It will also update transactionally: every other useQuery in your app will update (if necessary) at the same time, so that there's never inconsistent data onscreen
Also, how would that be practically different from using something like Jotai or Redux?It's not super different, although like react-query (/TanStack Query) or useSWR, Convex is used to manage server-side state, vs Jotai, Redux, Zustand, Recoil etc. managing client (and sometimes server) state oh you asked about adding it to a context, yeah not very different, except since Convex has its own store you don't need to expose the data on the context. If the data dependency is declared somewhere higher in the tree , that query will remain subscribed to
By saying I don't need to expose the data on the context, you mean I don't need to save it in a state, right?
Yeah, and you don't need to add this data to a React
<Context.Provider value={this data}>
Wait, so how would I access the query from a page then?
You could add it to a context, but since you mentioned it feeling weird to add there since only one page needs it, you could write
useQuery("user/getIntro", user)
in two different places in your app
once in _app.jsx
to declare a subscription to it, and another in the component/page to use the data
I think I'd expose it on a context, but that would be a global thing that every page of your app had access to, so if you want to prevent that you could duplicate the codeOhh, that works? I had no idea. I could write the useQuery in the context then, right? And I still wouldn't need to pass it as a value?
I'll give it a shot
If you've used react-query or swr this global cache idea is similar, although for Convex it's a global cache with an instant invalidation time
with Convex apps never read stale data, so the cached value for a query goes away once it's no longer subscribed to anywhere in the app
So, I have hope! But I also have a problem now
I need the user id for the query and I can't conditionally call the useQuery hook as per React's rules

If i move up the
useQuery
, user
will be null which will give me a convex error
oh, unless i do some validation in the convex function?ah shoot, yeah classic. Yeah you could make user/getIntro accept
null
, and write
here
(it can't accept undefined
, we prevent that from being sent as an argument)
We're looking at making it easier to conditionally call queries for this reasonIt works! And wow, that's genius
This whole thing
Seriously, I'm pretty amazed right now. That just solved a lot of problems I had in mind
You could also make another React component that does the useQuery, takes in user as a prop, and only render it if the user is not null. But making another component is a little annoying, so having the query work with null may be nicer until we have conditional queries
Yeah, it works pretty smoothly right now in the UserContext
Thanks a lot again guys, really appreciate it. I learned a lot today, both of convex and of react 😄. Also, is this concept in the docs anywhere? I don't remember reading anything about it. Perhaps I just missed it
The "trick" of using a useQuery just for the side-effect of subscribing (what in other systems would be called "priming the cache") is not, we should add it! The overall idea of the list of queries that an app is subscribed to being a global list should be covered (actually I'll check) but this would be a great example to make it clear.
Yeah, I don't remember reading about queries being global. I mean, I am aware that Convex aims to be a global state manager, but I wasn't quite sure how. Anyway, yeah! Definitely add some documentation about the cache priming, it's a great feature that I don't think I've seen in other technologies (not that I've tried many)
will do, thanks so much for the suggestion!
is this still the recommended way to deal with conditionally calling queries, or queries that depend on each other? @ballingt
Now 16 months later we use "skip" https://docs.convex.dev/client/react#skipping-queries
Convex React | Convex Developer Hub
Convex React is the client library enabling your React application to interact
thanks!