You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
useRouteContext returns undefined upon full page reload #2010
I can't remember where else we discussed this, but routeContext being used in a component is an anti-pattern if you're doing SSR. Let me explain why:
Hydration needs to be relatively synchronous. We can't wait for async assets before rendering
To accomplish this, anything that was async on the server is serialized down to the client and made ready before hydration
Context is not guarantied to be serializable, so we can't serialize it.
This means that on the initial hydration render of the app, the best "context" we could provide is the router context, which should also be isomorphic (e.g. if you supply something like a query client, it should be instantiated on both the server and the client).
We can't make hydration async, and we wouldn't want to either way
We can't make beforeLoad sync
We can't guaranty serialization of routeContext, and even if we could mix, we would still need to be able to wait on the async nature of it to build up the routeContext
Proposed solution:
Do not use routeContext in components.
Possibly we remove the Route.useRouteContext hook.
Removing the user access to the routeContext outside of the loader function would make it difficult for anyone using it to build something like a breadcrumbs component. Also, the route context does allow for patterns where you can in a single location permeate some options that'll be the same in the loader and in the component.
This sort of pattern is pretty handy and IMO it would be a shame to lose.
// src/routes/posts.tsximport{createFileRoute}from'@tanstack/react-router';import{useSuspenseQuery,queryOptions}from'@tanstack/react-query';exportconstRoute=createFileRoute('/posts')({beforeLoad: ({ params })=>{return{// only calculate what the queryOptions were oncepostOptions: queryOptions({queryKey: ['posts',params.postId], ... }),postCommentsOptions: queryOptions({queryKey: ['posts',params.postId,'comments'], ... })}},loader: async({ context })=>{awaitPromise.allSettled([// you can also choose not to await here, just kick off Querycontext.queryClient.ensureQueryData(context.postOptions),context.queryClient.ensureQueryData(context.postCommentsOptions),])},component: Posts})functionPosts(){const{ postOptions, postCommentOptions }=Route.useRouteContext();constpostQuery=useSuspenseQuery(postOptions);constpostCommentsQuery=useSuspenseQuery(postCommentsOptions);// ...}
If I remember correctly, for a period of time during the beta when the loaderData was dropped, the router was setup in a way to accept non-serializable content (like a queryClient, apiFetcher, etc.), but everything returned by a Route's beforeLoad function had to be serializable.
👆🏼Would something like this be feasible here?
This allows us to serialize the loaded route context objects and send it down during SSR, but still allow the hydration to happen on the client yes?
Or maybe we have an SSR toggle that enforces the route-context into being serializable? Although this'd cause a divergence which is something we probably do not want.
Activity
iamchanii commentedon Jul 23, 2024
Here is a workaround:
const router = createRouter({ ... }); + router.buildAndCommitLocation({}); + // but... why?tannerlinsley commentedon Jul 26, 2024
I can't remember where else we discussed this, but
routeContextbeing used in a component is an anti-pattern if you're doing SSR. Let me explain why:Hydration needs to be relatively synchronous. We can't wait for async assets before rendering
To accomplish this, anything that was async on the server is serialized down to the client and made ready before hydration
Context is not guarantied to be serializable, so we can't serialize it.
This means that on the initial hydration render of the app, the best "context" we could provide is the router context, which should also be isomorphic (e.g. if you supply something like a query client, it should be instantiated on both the server and the client).
We can't make hydration async, and we wouldn't want to either way
We can't make
beforeLoadsyncWe can't guaranty serialization of
routeContext, and even if we could mix, we would still need to be able to wait on the async nature of it to build up the routeContextProposed solution:
routeContextin components.Route.useRouteContexthook.routeContextinloaders.schiller-manuel commentedon Jul 26, 2024
This is not SSR related. just an ordinary SPA.
tannerlinsley commentedon Jul 27, 2024
Sorry, you are correct. This should be working for non-SSR renderings. However, the issue stands. What should we do?
SeanCassiere commentedon Jul 27, 2024
Removing the user access to the
routeContextoutside of theloaderfunction would make it difficult for anyone using it to build something like a breadcrumbs component. Also, the route context does allow for patterns where you can in a single location permeate some options that'll be the same in the loader and in the component.This sort of pattern is pretty handy and IMO it would be a shame to lose.
SeanCassiere commentedon Jul 27, 2024
If I remember correctly, for a period of time during the beta when the
loaderDatawas dropped, the router was setup in a way to accept non-serializable content (like a queryClient, apiFetcher, etc.), but everything returned by a Route'sbeforeLoadfunction had to be serializable.👆🏼Would something like this be feasible here?
tests: reproducer for #2010
schiller-manuel commentedon Jul 27, 2024
reproducer in #2045
tests: reproducer for #2010
schiller-manuel commentedon Aug 21, 2024
this is fixed in 1.49.1
tests: reproducer for #2010 (#2045)