Loading data in the <Route> with `useSuspenseQuery` forces suspending and scroll reset (w/ example)
I'm trying to fetch data in the loader of a <Route /> that depends on search params and I'm not able to figure out how to do it right (?).
I created a sample repo: https://stackblitz.com/edit/tanstack-router-aiytgf7a?file=src%2Froutes%2Findex.tsx
Current behavior
I'm changing the query param and my app goes blank, suspends and renders the new data with a scroll reset (worst case).
Desired behavior
I'm changing the query parm, partial cache match, the stale data remains on screen, fetch in the background, data is replaced. No scroll reset, no suspending.
Reproduce
Go to sample repo. Scroll down, click on a button. Wait for the scroll reset, see the green screen (pending component). I want to get rid of both (green screen + scroll reset)
fmart
StackBlitz
data-fetching-in-loader - StackBlitz
Run official live example code for Router Basic React Query File Based, created by Tanstack on StackBlitz
15 Replies
optimistic-goldOP•3mo ago
I created a second sandboy where I use
resetScroll=false
and startTransition
from react. Still no success: https://stackblitz.com/edit/tanstack-router-6cmyjlwc?file=src%2Froutes%2Findex.tsxfmart
StackBlitz
data-fetching-in-loader (duplicated) - StackBlitz
Run official live example code for Router Basic React Query File Based, created by Tanstack on StackBlitz
wee-brown•3mo ago
The fork seems to have the desired behavior for me
In your first repro the default
pendingMs
I think is the same as your timeout (1500ms) so you sometimes get a suspense hit
Not sure how to avoid it altogether, startTransition is indeed the approach with useNavigate() but I haven't run into this scenario myself yet with linksoptimistic-goldOP•3mo ago
Hey! Thanks for looking into it. Yeah. I got it to work on the fork. But in my actual app I still see flashing screens and scroll bouncing.
wee-brown•3mo ago
are you overriding the
defaultPendingMs
when creating the router, by any chance?optimistic-goldOP•3mo ago
I was able to crack it after all. The problem was a.) the missing
resetScroll=false
properties on all navigations and b.) there was a slight mismatch between my useSuspenseQuery
paramters and the ensureQueryData
parameters. These two have to be kept in sync 100000%. If they aren't the useSuspenseQuery
will suspend and if it's not wrapped in a top-level <Suspense />
escalate to the Route-level pendingComponent
.wee-brown•3mo ago
Glad you found it! Are you still overriding
pendingMs
on route level to avoid triggering the route's pendingComponent
when clicking the link?optimistic-goldOP•3mo ago
yes, for sure. I think the default is 1s and for longer running queries that is not enough. As soon as the component suspends the scroll will be reset. And while it suspends the old data is shown. The only downside (AFAICT) is that I'm not able to show the pending component on first load with this approach.
wee-brown•3mo ago
I'm guessing you could for example make a wrapper
Link
component with a custom withTransition={true|false}
prop that passes the to
to the router's Link
(so you can cmd+click or right-click+open in new tab) but also dynamically (depending on the withTransition
value) sets an onClick
handler that prevents the default event and does a startTransition
+navigate(props)
. It should let you omit the custom pendingMs
if I understand everything correctly. Not sure if there's currently any better way to integrate a link within Suspense. With withTransition={true}
you'd then override the default link behavior and opt into Suspense loading with a navigate()
, when set to false
you'd get default link behavioroptimistic-goldOP•3mo ago
Trying to wrap my head around some new concepts 😵💫. So, I'm currently overwriting the
To give the loader more time I'm overwriting
pendingMs
to prevent the route from showing the pendingComponent
. The pendingComponent
will reset the scroll state of the user, leading to a bad UX.
The flow goes like this:
1. User clicks button
2. Query param is updated
3. Route is marked as pending
4. Tanstack router flow (loader is executed)
5. If the loading takes too long (>1s) the pending component will reset the scroll state of the user (bad)To give the loader more time I'm overwriting
pendingMs
with a larger value. Just repeating this so we're talking about the same thing.genetic-orange•3mo ago
optimistic-goldOP•3mo ago
My knowledge about
startTransition
is minimal. The Query docs mention that useSuspenseQuery
does not have placeholderData
and we should use startTransition
. But AFAICT my problem is not the call touseSuspenseQuery
. My problem is the route loader.
The loader must finish for the route to be marked as resolved
. As soon as it does so, the page will be rerendered and useSuspenseQuery
can fetch the data from the cache (the cache was seeded in the loader with ensureQueryData
.
What I want to achieve is classic stale while revalidate
behavior.
1. Query params get updated
2. Stale data is shown
3. Fetch in the background completes (can take a long time ... no need to suspend or show pendingComponent
)
4. Render page with new data
I'm trying to understand if your response was addressing this flow, @pleunv .
Thanks @Manuel Schiller , will take a look!
@Manuel Schiller hm, I don't think the GH thread offers any solution. There are a couple proposals but they all fall short IMO. But it's good to know that many people seem to have this problem. It seems that Tanstack Router does not have a solution for this problem currently.genetic-orange•3mo ago
useDeferredValue?
optimistic-goldOP•3mo ago
Oh, kinda brushed over that part. Will read the article from Frontend Masters tomorrow. See if that helps and report back. Thanks!
genetic-orange•3mo ago
wee-brown•3mo ago
What I had in mind was something like this (not tested it):
I've not used
useDeferredValue
in this context yet myself but that looks like perhaps a slightly easier solution
The advantage of the link wrapper is that you could get access to the pending state and show a spinner. There's other ways to do this (e.g. using MatchRoute
or looking at the global navigation status iirc - but in these cases you can't know for sure if the link was the initiator of the navigation, you need an event-based solution for that)
edit: Doesn't seem to work, not entirely sure why. Seems to conflict with transitions that router is running internally