Edwin
Edwin•3y ago

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
sujayakar
sujayakar•3y ago
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.
Edwin
EdwinOP•3y ago
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
Edwin
EdwinOP•3y ago
No description
Edwin
EdwinOP•3y ago
As you can see from the dashboard logs, it logs it twice
No description
Edwin
EdwinOP•3y ago
No description
sujayakar
sujayakar•3y ago
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?
Edwin
EdwinOP•3y ago
yup to both same goes with my query on a page
sujayakar
sujayakar•3y ago
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.
Edwin
EdwinOP•3y ago
No description
Edwin
EdwinOP•3y ago
it logs twice as well
sujayakar
sujayakar•3y ago
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.
Edwin
EdwinOP•3y ago
yeah, the UserContext is set up on _app.js. is it supposed to be somewhere else?
Edwin
EdwinOP•3y ago
No description
Edwin
EdwinOP•3y ago
or is perhaps the hierarchy wrong?
sujayakar
sujayakar•3y ago
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.
Edwin
EdwinOP•3y ago
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
Edwin
EdwinOP•3y ago
No description
sujayakar
sujayakar•3y ago
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?
Edwin
EdwinOP•3y ago
yeah, they seem to be the same
Edwin
EdwinOP•3y ago
No description
No description
Edwin
EdwinOP•3y ago
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
sujayakar
sujayakar•3y ago
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.
jamwt
jamwt•3y ago
two tabs open with the app? while you guys get cleverer I'll get simpler just in case and ask the dumb questions 😄
Edwin
EdwinOP•3y ago
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
jamwt
jamwt•3y ago
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
sujayakar
sujayakar•3y ago
@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
Edwin
EdwinOP•3y ago
yup, i do
sujayakar
sujayakar•3y ago
I haven't used them a ton, but I think the profile option can help diagnose what's causing rerenders.
Edwin
EdwinOP•3y ago
oh sweet i'll look into it for a bit
sujayakar
sujayakar•3y ago
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.
lee
lee•3y ago
Hi I'm available to continue debugging if you'd like (west coast time 😆 )
Edwin
EdwinOP•3y ago
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 twice
lee
lee•3y ago
If you could export or screenshot the rerender, that would be awesome, thanks! I'll try profiling our test auth0 app to compare
Edwin
EdwinOP•3y ago
i believe you can load the profile on react devtools. let me know if it doesn't work
ballingt
ballingt•3y ago
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)
Edwin
EdwinOP•3y ago
ohh, interesting. i don't think i am using strict mode though. i don't remember enabling it, but i'll check again
ballingt
ballingt•3y ago
In Next there's a <Strict> wrapper sometimes
sshader
sshader•3y ago
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)
Edwin
EdwinOP•3y ago
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
ballingt
ballingt•3y ago
Oops, sorry for the goose chase!
Edwin
EdwinOP•3y ago
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?
ballingt
ballingt•3y ago
If you can raise that useQuery up to a component that remains rendered between navigations then it will remain loaded
Edwin
EdwinOP•3y ago
I see, that makes sense. Thanks a lot!
ballingt
ballingt•3y ago
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
Edwin
EdwinOP•3y ago
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. 🙌
ballingt
ballingt•3y ago
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!
Edwin
EdwinOP•3y ago
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
ballingt
ballingt•3y ago
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)
Edwin
EdwinOP•3y ago
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?
ballingt
ballingt•3y ago
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
Edwin
EdwinOP•3y ago
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?
ballingt
ballingt•3y ago
Yeah, and you don't need to add this data to a React <Context.Provider value={this data}>
Edwin
EdwinOP•3y ago
Wait, so how would I access the query from a page then?
ballingt
ballingt•3y ago
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 code
Edwin
EdwinOP•3y ago
Ohh, 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
ballingt
ballingt•3y ago
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
Edwin
EdwinOP•3y ago
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
Edwin
EdwinOP•3y ago
No description
Edwin
EdwinOP•3y ago
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?
ballingt
ballingt•3y ago
ah shoot, yeah classic. Yeah you could make user/getIntro accept null, and write
const getIntro = useQuery("user/getIntro", user || null);
const getIntro = useQuery("user/getIntro", user || null);
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 reason
Edwin
EdwinOP•3y ago
It 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
lee
lee•3y ago
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
Edwin
EdwinOP•3y ago
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
ballingt
ballingt•3y ago
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.
Edwin
EdwinOP•3y ago
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)
ballingt
ballingt•3y ago
will do, thanks so much for the suggestion!
json
json•11mo ago
is this still the recommended way to deal with conditionally calling queries, or queries that depend on each other? @ballingt
ballingt
ballingt•11mo ago
Convex React | Convex Developer Hub
Convex React is the client library enabling your React application to interact
json
json•11mo ago
thanks!

Did you find this page helpful?