Skip to content

useRouteContext returns undefined upon full page reload #2010

Closed

Description

@schiller-manuel

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

Activity

  1. iamchanii commented on Jul 23, 2024

    @iamchanii
    Contributor

    Here is a workaround:

      const router = createRouter({
        ...
      });
    
    + router.buildAndCommitLocation({});
    + // but... why?
  2. tannerlinsley commented on Jul 26, 2024

    @tannerlinsley
    Member

    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.
  3. schiller-manuel commented on Jul 26, 2024

    @schiller-manuel
    CollaboratorAuthor

    This is not SSR related. just an ordinary SPA.

  4. tannerlinsley commented on Jul 27, 2024

    @tannerlinsley
    Member

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

  5. SeanCassiere commented on Jul 27, 2024

    @SeanCassiere
    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);
       // ...
    }
  6. SeanCassiere commented on Jul 27, 2024

    @SeanCassiere
    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.
  7. schiller-manuel commented on Jul 27, 2024

    @schiller-manuel
    CollaboratorAuthor

    reproducer in #2045

  8. schiller-manuel commented on Aug 21, 2024

    @schiller-manuel
    CollaboratorAuthor

    this is fixed in 1.49.1

  9. added a commit that references this issue on Aug 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions