router
router copied to clipboard
useRouteContext initially undefined
Describe the bug
When loading the page, 'useRouteContext' is undefined. When client side navigating to the page, it's defined.
Your Example Website or App
https://tanstack.com/router/latest/docs/framework/react/examples/basic-ssr-file-based
Steps to Reproduce the Bug or Issue
It's can be reproduced in the "basic-ssr-file-based" example. If you go from "home" to "post" in browser, the context is defined. If you reload the page while standing on "post" page, it's undefined.
Expected behavior
useRouteContext being defined both during SSR load and Client side routing.
Screenshots or Videos
No response
Platform
Linux
Additional context
No response
I've tested this using the "basic-ssr-file-based" in the sandbox below (with all the packages upgraded to latest). In the sandbox below, I was not able recreate the issue you described.
https://stackblitz.com/edit/github-djwvqx?file=src%2Froutes%2Fposts%2Findex.tsx
Please attach a minimal reproduction of the issue you facing with any steps required to replicate the scenario to observe the issue.
@SeanCassiere was able to reproduce it in the sandbox you linked. Navigating to /posts and refreshing the page causes a hydration error and: PostsIndexComponent.context undefined
Reproduction steps:
- Open the stackblitz example and open the preview in a new tab.
- Directly visit
/posts/. - Observe the error message on the screen and in the console.
- Observe the hydration failure message in the console.
Edit: I haven't seen this behaviour on client-side rendering ONLY apps.
I have this same issue right now. When using SSR the context for the first paint on the client is undefined.
My use case is I am setting the page title in the __root.tsx meta section from the context, when on the server context is there, when navigating on the client the context is there, just on the initial paint on the client the context is undefined. I am not even expecting my server context to be there, the initial context from the createRoute is missing on the client as well.
Wanted to bump with the recent #1907 being merged, that this issue still exists.
With the code snippet Route.useRouteContext({ select: (ctx) => { console.log(ctx); return ctx; }), ctx will, initially on page load, be printed out in the browser as undefined.
Curious what your use case is here, because this is a tricky situation...
Context is guaranteed to be available by the time beforeLoads and loaders are called, but hydration is quite different because it can't be asynchronous. Everything needs to be available and ready to render by the time we call root.hydrate(). So while data from loaders, etc are serialized down to the client during SSR, context is not. This is primarily because the things we put into context aren't serializable, e.g. something like QueryClient.
So a few key points:
- Many content-y values are not serializable.
- Router context is synchronous. This should never need to be serialized.
- Route context is asynchronous and relies on the return of
beforeLoad, which doesn't run before hydration
What I propose:
- Educate that router context is primarily meant to be used in beforeLoad and loaders since these lifecycle events live outside of React where we don't have access to React's context.
- Educate that if you need to rely on a synchronous context value in your components, you should be placing it in a React context provider. Having the same variable in router context and react context is fine!
- Educate that if you need to rely on an asynchronous value during rendering, it needs to be returned in a
loaderand be serializable. - Educate that route context, like router context is primarily meant to be consumed in
beforeLoadandloader, not components. - Potentially deprecate
Route.useRouteContext()to "enforce" the above.
@tannerlinsley I posted a question recently on Discord that could describe the use case of wanting to serialize "some" asynchronous context values to the client in the case of accessing route context in meta.
If I use
meta: ({match: { routeContext: { someField } } }) => { ... }or
meta: ({match: { context : { someField } } }) => { ... }... in both cases, they are undefined. I do not want to have the data I am accessing in
metabe returned in theloader, because the data was loaded in a parent match'sbeforeLoadfunction. Having the data returned inloaderwill serialize it to the client and cause the data to be duplicated in the initial SSR'd HTML payload.
Closing this issue since the context undefined bug looks to have been fixed in this scenario upon retesting, as well as the hydration failure being resolved.
// basic-ssr-file-based/src/routes/posts/index.tsx
import { createFileRoute, useRouteContext } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/')({
component: PostsIndexComponent,
})
function PostsIndexComponent() {
const context = useRouteContext({ strict: false })
console.log('PostsIndexComponent context:', context)
return <div>Select a post.</div>
}
Screenshot of the console when navigating from "Home" to "Posts".
Screenshot of the console when directly navigating to "localhost:3000/posts".