Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useRouteContext returns undefined upon full page reload #2010

Closed
schiller-manuel opened this issue Jul 22, 2024 · 8 comments
Closed

useRouteContext returns undefined upon full page reload #2010

schiller-manuel opened this issue Jul 22, 2024 · 8 comments

Comments

@schiller-manuel
Copy link
Contributor

schiller-manuel commented Jul 22, 2024

Describe the bug

Upon full page reload, useRouteContext returns undefined.

Your Example Website or App

https://stackblitz.com/edit/tanstack-router-qqb8ia?file=src%2Froutes%2Ffoo%2Findex.tsx&preset=node

Steps to Reproduce the Bug or Issue

  1. go to https://stackblitz.com/edit/tanstack-router-qqb8ia?file=src%2Froutes%2Ffoo%2Findex.tsx&preset=node
  2. click on "Foo" link
  3. see that "Hello /foo/index!" is shown
  4. full reload page
  5. see that error message "context is undefined" is shown

Expected behavior

route context is never undefined

Screenshots or Videos

Screen.Recording.2024-07-22.at.22.31.33.mov

Platform

  • OS: macOS
  • Browser: any
  • react-router version: 1.46.7

Additional context

No response

@iamchanii
Copy link
Contributor

Here is a workaround:

  const router = createRouter({
    ...
  });

+ router.buildAndCommitLocation({});
+ // but... why?

@tannerlinsley
Copy link
Collaborator

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.
  • Only use routeContext in loaders.

@schiller-manuel
Copy link
Contributor Author

schiller-manuel commented Jul 26, 2024

This is not SSR related. just an ordinary SPA.

@tannerlinsley
Copy link
Collaborator

Sorry, you are correct. This should be working for non-SSR renderings. However, the issue stands. What should we do?

@SeanCassiere
Copy link
Member

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.tsx
import { createFileRoute } from '@tanstack/react-router';
import { useSuspenseQuery, queryOptions } from '@tanstack/react-query';

export const Route = createFileRoute('/posts')({
    beforeLoad: ({ params }) => {
        return { // only calculate what the queryOptions were once
            postOptions: queryOptions({ queryKey: ['posts', params.postId], ... }),
            postCommentsOptions: queryOptions({ queryKey: ['posts', params.postId, 'comments'], ... })
        }
    },
   loader: async ({ context }) => {
      await Promise.allSettled([ // you can also choose not to await here, just kick off Query
          context.queryClient.ensureQueryData(context.postOptions),
          context.queryClient.ensureQueryData(context.postCommentsOptions),
     ])
   },
  component: Posts
})

function Posts() {
   const { postOptions, postCommentOptions } = Route.useRouteContext();
   const postQuery = useSuspenseQuery(postOptions);
   const postCommentsQuery = useSuspenseQuery(postCommentsOptions);
   // ...
}

@SeanCassiere
Copy link
Member

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.

schiller-manuel added a commit that referenced this issue Jul 27, 2024
@schiller-manuel
Copy link
Contributor Author

reproducer in #2045

schiller-manuel added a commit that referenced this issue Aug 21, 2024
@schiller-manuel
Copy link
Contributor Author

this is fixed in 1.49.1

schiller-manuel added a commit that referenced this issue Aug 21, 2024
this was fixed in 1.49.1, keep this test to avoid future regressions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants