Replies: 3 comments 5 replies
-
@Ephem one open question that came to my mind today is how this would integrate with the planned |
Beta Was this translation helpful? Give feedback.
-
Excited that RQ is thinking about this! We've been working on our own enforcement of this on top but it'd be nice to have it built in. One concern with the Context provider approach is that it makes it harder to do incremental migrations of existing code. In our experience you rarely have nice and neat "clear" branches of the app you can cordon off to enforce prefetching in. In practice we pull up queries one at a time as we have time to track down requests in component trees and refactor fetching logic out of hooks so they can be prefetched by the router. This can take some time before we can say "don't allow fetching in render here". One option is to have something like |
Beta Was this translation helpful? Give feedback.
-
Will there be support for regular |
Beta Was this translation helpful? Give feedback.
-
Prefetching is hard, but it’s also one of the best ways to get data to your users as fast as possible. There are also multiple ways to prefetch, the most common ones are:
usePrefetchQuery
queryClient.prefetchQuery
Prefetching or “hoisting your data requirements” is especially recommended when using
suspense
, because it can easily trigger request waterfalls: CallinguseSuspenseQuery
twice in the same component gives you a waterfall. Calling it in siblings within the sam suspense boundary almost gave us a waterfall in React19.The problem
The main problem we are seeing with prefetching is code-dislocation. Just calling
useSuspenseQuery
in your component and having it work is very convenient, but without a compiler likerelay
, it won’t be possible to extract those data requirements and trigger prefetching somewhere else automatically.So what we are left with is duplicating what we do: Have a component call
useSuspenseQuery
with certain options, and use the same options in your route loader or event handler to do prefetching. That’s an okay trade-off, however, it comes with two additional problems:Under-prefetching
Let’s say we write a component that fetches from
/issues/$id
, so we add prefetching for that to our route loader. Everything is fine and we ship it. Next, someone else adds fetching comments for that issue to the component, so there will be a new query that fetches from/issues/$id/comments
. It’s quite common to forget to add that fetch to the route loader, which leaves us with a new request waterfall: We are only stating to fetch comments after we are done fetching the issue details, even though they could load in parallel.Over-prefetching
On the flip-side, let’s assume we do prefetch issue details and comments correctly in the route loader, but then, we decide to move comments to an entirely different page. When the component moves, so does the query, but the prefetching in the route loader might stay. That will fire an unnecessary request for
/issues/$id/comments
, and it might even block the page from loading until that request is done.Proposed solution
To address both problems, the idea is to introduce a
<QueryStrictMode>
component in DEV mode only (similar to the React StrictMode component) that will do the following:useSuspenseQuery
would suspend the component with a new promise, it would log an error. This will alert developers that they are using a suspense query, but haven’t prefetched it. When the component renders, it should merely pick up a promise that is already in the cache. This would address the under-prefetching issue.Potentially, we could make this component’s logger customisable (defaulting to
console.error
) so that you can opt out of logging for certain queries:Implementation details
Under-prefetching protection is relatively simple to implement: We can check before we
throw fetchOptimistic
if there is a promise in the cache already. If not,fetchOptimistic
would trigger the fetch, which isn’t really what we want.Over-prefetching protection is a bit more involved. I think we could subscribe to the QueryCache directly, and whenever a fetch starts while there is no active observer for the given key, we consider it a prefetch (note to self: we have to ignore while we’re in
isRestoring
mode because restores from an external storage shouldn’t be considered prefetches). Then, we start a timer which we can cancel as soon as an observer subscribes or the query gets garbage collected.Beta Was this translation helpful? Give feedback.
All reactions