T
TanStackβ€’2y ago
sensitive-blue

data loading with nested routes

I have two nested routes, both have a loader defined. As an example, the parent loader takes 2 seconds and the child loader takes 1 second. I can see that the loaders kick off in parallel, but I an see the pending components being rendered as soon as one loader has finished. This means that after two seconds, the parent route renders, and I see another pending component for the child route for one second. According to the docs, I was expecting one loader for 3 seconds (unless I use defer):
TanStack Router is designed to run loaders in parallel and wait for all of them to resolve before rendering the next route.
https://tanstack.com/router/latest/docs/framework/react/guide/deferred-data-loading Am I just interpreting this wrongly?
Deferred Data Loading | TanStack Router Docs
TanStack Router is designed to run loaders in parallel and wait for all of them to resolve before rendering the next route. This is great most of the time, but occasionally, you may want to show the user something sooner while the rest of the data loads in the background. Deferred data loading is a pattern that allows the router to render the ...
24 Replies
sensitive-blue
sensitive-blueOPβ€’2y ago
sensitive-blue
sensitive-blueOPβ€’2y ago
Dominik Dorfmeister
StackBlitz
Router Quickstart File Based Example (forked) - StackBlitz
Run official live example code for Router Quickstart File Based, created by Tanstack on StackBlitz
sensitive-blue
sensitive-blueβ€’2y ago
I may not have understood the question completely here. Are you looking to have the whole app suspend for that time? because if not, is it not working?
sensitive-blue
sensitive-blueOPβ€’2y ago
not the whole app, but the whole "products route". So I would expect to see: - the nav bar at the top - a "loading..." indicator until both the product route and the product details route has loaded right now, I see the "loading...." (the loader of the parent route) for 2 secs followed by "Products loading..." (which is the content of the parent route + the loader of the child route) for another second. This basically will result in loaders cascading down if we have a couple of layers I guess this is what suspense would do if the same suspense boundary were used
sensitive-blue
sensitive-blueβ€’2y ago
Yea... TSR doesn't do that. Both loaders are kicked-off at the same-time, but the actual components are immediately shown after their corresponding loader has finished. I'm surprised I haven't thought about that before πŸ€” Then again I rarely run into it since data gets optimistically cached with preload into Query and I pretty much suspend everywhere.
sensitive-blue
sensitive-blueOPβ€’2y ago
is there a way to have the loader integrate with the suspense boundary ? what I would want is to kick off loading in the route loaders, but still show and aggregate them in the same suspense boundary I tried kicking off the loading in the loader, and then call useSuspenseQuery in the component to show the suspense boundary, but the result was this error:
A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.
A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition.
have you seen this before? hmm, I'm getting this even when not using the loader; let me try to repro it
sensitive-blue
sensitive-blueOPβ€’2y ago
Dominik Dorfmeister
StackBlitz
Router Quickstart File Based Example (forked) - StackBlitz
Run official live example code for Router Quickstart File Based, created by Tanstack on StackBlitz
sensitive-blue
sensitive-blueβ€’2y ago
Heading offline since it's 3am here in NZ and I have lectures starting at 9am and I'll be back in the evening. Do you mind pinging someone in the Router Maintainers for this.
firm-tan
firm-tanβ€’2y ago
I think that is something @Tanner Linsley needs to have a look at
fair-rose
fair-roseβ€’2y ago
Yep
sensitive-blue
sensitive-blueOPβ€’2y ago
aren't errors around suspense / transition nice πŸ˜‚ @Tanner Linsley there must be some sort of suspense integration, because if I just useSuspenseQuery in my components, the defaultPendingComponent is shown. The error only shows up if I don't have a pending component defined... but still, I can't aggregate fetches from multiple routes into the same suspense boundary, so that one loader is shown until data for all sub-routes is loaded. Just summarizing the issue here - judging from the docs, I thought this is how it should behave per default if I don't use defer πŸ€”
fair-rose
fair-roseβ€’2y ago
Welcome to fine grained vs transitions The problem here is that because we're using uSES, suspense boundaries are not batched So they don't transition/suspend together
sensitive-blue
sensitive-blueOPβ€’2y ago
oh interesting. That means querying would likely have the same problem?
fair-rose
fair-roseβ€’2y ago
Yep And believe me, granular (non batched) suspense is annoying AF This is why I'm so excited about the prospect of useMemo(() => use(context))
sensitive-blue
sensitive-blueOPβ€’2y ago
@Tanner Linsley I think it must be something else. Here's a quick reproduction with query alone, with two queries in different components in the same suspense boundary: https://stackblitz.com/edit/tanstack-query-jkkye5?file=src%2Findex.jsx&preset=node Note that: - both start fetching immediately - "one" resolves after 1 second, "two" resolves after 3 seconds - the suspense fallback is shown until both are finished (so we see one 3-second loader) - data pops in at the same time after that With the router, the behaviour I saw was that: - both start fetching immediately - after one second, data for the first result pops in, and I see the loader being pushed down In query, I get that behaviour if I move both queries to separate suspense boundaries. So maybe this is just a bug in router? The docs say it should show data only after all routes have loaded, unless you use defer and <Await> ...
Dominik Dorfmeister
StackBlitz
Query Simple Example (forked) - StackBlitz
Run official live example code for Query Simple, created by Tanstack on StackBlitz
fair-rose
fair-roseβ€’2y ago
Are you awaiting that data in loaders? It only waits on loaders. If you’re expecting it to wait for multiple suspends, then it won’t work
sensitive-blue
sensitive-blueOPβ€’2y ago
Yes I'm awaiting data in loaders
sensitive-blue
sensitive-blueOPβ€’2y ago
You can see this in the original reproduction from above: https://stackblitz.com/edit/tanstack-router-gv16p4
Dominik Dorfmeister
StackBlitz
Router Quickstart File Based Example (forked) - StackBlitz
Run official live example code for Router Quickstart File Based, created by Tanstack on StackBlitz
sensitive-blue
sensitive-blueOPβ€’2y ago
another interesting observeration is that it seems to work differntly if I have a pendingComponent vs not having one. here is another fork of the above example, but with all the defaultPending... settings removed. You can see that if you click on Product 5, the route will only render once all data has been fetched: https://stackblitz.com/edit/tanstack-router-xuegwh?file=src%2Fmain.tsx but if you comment in the defaultPendingComponent, you will see the loaders "pop in" one at a time
Dominik Dorfmeister
StackBlitz
Router Quickstart File Based Example (forked) - StackBlitz
Run official live example code for Router Quickstart File Based, created by Tanstack on StackBlitz
sensitive-blue
sensitive-blueOPβ€’2y ago
one of the reasons why a pending component is useful is that without it, I'll just see an empty white page on the first page load. You can see that if you reload the preview while being on /products/5. Essentially, I would expect the router to behave the same no matter if there is a pending component or not. It becomes quite obvious if you set defaultPendingComponent: () => null, and see that behaves differntly than if no pending component was defined ... I think this is the only major thing that bothers me right now. I don't think it's intended behaviour πŸ˜…
sensitive-blue
sensitive-blueOPβ€’2y ago
a very wild guess is that this resolve() call here: https://github.com/tanstack/router/blob/cafae105576d44204fc69cff5d182d4e745e89b3/packages/react-router/src/router.ts#L1514 resolves the promise for a given route eagerly, without waiting for other pending routes, which leads to the component being rendered immediately ?
GitHub
router/packages/react-router/src/router.ts at cafae105576d44204fc69...
πŸ€– Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering. - TanStack/router
sensitive-blue
sensitive-blueOPβ€’2y ago
hm, I can get the behaviour that I want if I: - comment out those two lines: https://github.com/tanstack/router/blob/cafae105576d44204fc69cff5d182d4e745e89b3/packages/react-router/src/router.ts#L1513-L1514 - add wrapInSuspense: false, to my child route; this make sense because then the child route picks up the suspense boundary from the parent, and that joins the two. Not sure what my commenting out does / breaks though πŸ˜… Any insights from maintainers would be appreciated ❀️
GitHub
router/packages/react-router/src/router.ts at cafae105576d44204fc69...
πŸ€– Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering. - TanStack/router
fair-rose
fair-roseβ€’2y ago
Looking now Alright, let me clear a few things up: - Each route's pending timeout is started at the same time in parellel - If that pending timeout is reached before the loader finishes, it will force the pending component to show until the loader finished for that match - Once you show any pending component for the next route, the router "transition" is short circuited - If you have short circuited the transition with a pending timeout and run into another child match that is in the "pending" state, its pending component will also show. So if you have route matches with loaders that take [1s, 2s, 3s] and pending is set to 0, then that means you'll see 3 pending states as the loaders resolve and you progressively see things render down. If you have [3s, 2s, 1s] and pending set to 0, Then you should only see one pending state for 3 seconds. From my testing in your examples, this holds true. - The default pending timeout is 1 second So if you remove the default you set, you'll see pending states after 1 second for This is merely because after some deliberation, many of us determined that 1 second was a decent default to force a pending component to show, if it's configured If you pump this timeout up to something ridiculous You'll see that the router transition will pause on the current screen until all loaders are resolved, then show the next screen From these expectations, I see no bug, but possibly a misunderstanding of the features or possibly a bad default. One optimization I would consider is changing the behavior for first time loads. It's true that if you want to lean into transitions more and not show things until everything is ready, then you likely don't want that experience on first load, especially if it's a slow page and you just see empty spaces until things resolved. It would involve simply detecting the first load for a router and forcing the pending states immediately
sensitive-blue
sensitive-blueOPβ€’2y ago
yeah the first load experience improvements would definitely be helpful. If you have some time today evening (my evening, your morning πŸ˜… ) I can show you a couple of things in the sandbox on a call. I think this would be easiest πŸ™‚

Did you find this page helpful?