TanStack / router

🤖 Fully typesafe Router for React (and friends) w/ built-in caching, 1st class search-param APIs, client-side cache integration and isomorphic rendering.
https://tanstack.com/router
MIT License
8.21k stars 649 forks source link

`useRouteContext` returns `undefined` upon full page reload #2010

Closed schiller-manuel closed 2 months ago

schiller-manuel commented 3 months ago

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

https://github.com/user-attachments/assets/d8f0989c-7541-4771-8c98-65e80594df1a

Platform

Additional context

No response

iamchanii commented 3 months ago

Here is a workaround:

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

+ router.buildAndCommitLocation({});
+ // but... why?
tannerlinsley commented 3 months ago

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:

Proposed solution:

schiller-manuel commented 3 months ago

This is not SSR related. just an ordinary SPA.

tannerlinsley commented 3 months ago

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

SeanCassiere commented 3 months ago

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 commented 3 months ago

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?

schiller-manuel commented 3 months ago

reproducer in https://github.com/TanStack/router/pull/2045

schiller-manuel commented 2 months ago

this is fixed in 1.49.1