authenticated routes
I am implementing authenticated routes and was following the guide in https://tanstack.com/router/v1/docs/framework/react/guide/authenticated-routes#authentication-using-react-contexthooks where it talks about authenticated routes for auth state stored in a Context. It all makes sense but I'm struggling with one thing -
My setup
My react Context gets the state for whether the user is authenticated or not by making an api call to
/user and if the response is valid and the user details are fetched successfully, it stores that state in the context as user. This is the state I am using to validate whether the user is logged in or not.
Question
Given my auth state depends on the user state that is going to be fetched, I don't want the check in beforeLoad to happen before that fetch and setting of state is complete. My current code is as follows but even if the user is logged in, it goes to login.
The logic of the behavior makes sense but what is the recommended way to handle the isLoading state in such a setup?Authenticated Routes | TanStack Router React Docs
Authentication is an extremely common requirement for web applications. In this guide, we'll walk through how to use TanStack Router to build protected routes, and how to redirect users to login if th...
75 Replies
automatic-azure•11mo ago
You did not show where you make the request for the user
i'd use
@tanstack/query to make the request for the user (so I can control how it gets cached), and I would fetch the user in the /_authenticated's beforeLoad, then adding it to the router's context by returning an object from beforeLoadharsh-harlequinOP•11mo ago
I make the request in the user context. The nesting looks as follows -
I am using tanstack query for the fetching and caching. My main issue is that while the query is loading, the
beforeLoad in _authenticated is already completed and it shows no one is logged in. Could you give me an example snipped for what you are suggesting? I can't call a hook in beforeLoad so I am bit confused by what you said
Also when I logout, the beforeLoad doesn't re-compute so how should I be handling that?automatic-azure•11mo ago
router.invalidate()
continuing-cyan•11mo ago
you can supply hook values via the routerprovider
<RouterProvider context={{foo: 'bar'}>harsh-harlequinOP•11mo ago
I actually do provide it there
My issue was that the GET /user has not finished fetching when I'm passing the values in
<RouterProvider context={{ user: UserData }}>
And so I'm passing in null
Then when it makes the check in beforeLoad, it redirects to login (even though it is still fetching /user)
For now I'm not rendering the children inside my <UserProvider> until the /user query has finished fetching. This way when I set the user state in the <RouterProvider> I know it has been fetched. Does that make sense or is there a simpler way to do this?continuing-cyan•11mo ago
you could pass a promise in there
and await that promise in the beforeLoad
you could also invert the logic and not use a UserContextProvider but instead use a loader / beforeLoade in __root
harsh-harlequinOP•11mo ago
Both interesting approaches!
1. Passing in a promise - I was thinking about having this state available across the app and hence using a context with state in it. Passing a promise would be without TSQ I'm guessing? Like just pass in an axios fetch and let it resolve and then take care of the auth state? In this case, if I want that state to be available, I'd just have the user context inside the authenticated routes with another TSQ fetch made?
2. Use a loader/beforeLoad in __root - Where can I understand loaders better? Also same issue of wanting this user state across my app as above. Also how would I access this in the _authenticated beforeLoad?
Not sure if I'm missing something obvious here but hope my questions make sense! Thanks for the help!
continuing-cyan•11mo ago
1. you could still use a context, you would just have to resolve said promise to signal that now the data is available. you don't have to do this for react, but for router, as router is not living in react world.
2. e.g here https://tanstack.com/router/v1/docs/framework/react/guide/data-loading and here https://tanstack.com/router/v1/docs/framework/react/guide/external-data-loading
in 2 you could still render a provider inside your _root route component to provide the value to all downstream react components
if you return something from a beforeLoad, it will be put into the router context and child routes will be able to access it in e.g. beforeLoad (passed in as an arg)
harsh-harlequinOP•11mo ago
ah makes sense!
in (1), I'd be making that GET /user twice though right? One for the promise and one for the context?
continuing-cyan•11mo ago
not necessarily
I would go for (2)
harsh-harlequinOP•11mo ago
Right. I guess you're suggesting using the router context instead of my own user context here in (2)?
automatic-azure•11mo ago
Why this when he could pass the promise resulting from
fetch()?continuing-cyan•11mo ago
also possible, sure
you need to be aware of the two worlds. react vs router.
a router context is not a react context (nor vice versa)
you can access the router context from react (via
useRouteContext)harsh-harlequinOP•11mo ago
right that makes sense now
continuing-cyan•11mo ago
maybe you can just call https://tanstack.com/query/v5/docs/reference/QueryClient/#queryclientensurequerydata in the
beforeLoad to ensure that you have a logged in user. and use TSQ as your cache
you can then either read that value from query, or from the route context if you choose to return it from beforeLoad
btw, beforeLoad is executed before each navigation. so if this is not cached, it would call your server for each navigation.harsh-harlequinOP•11mo ago
I got a bit confused there... sorry
https://discord.com/channels/719702312431386674/1330884061144551427/1330986250642919556 could you first explain how this would be possible?
continuing-cyan•11mo ago
where is this pointing to?
harsh-harlequinOP•11mo ago
Or maybe I can just explain what I'm thinking right now
1. In the beforeLoad in the __root, get the user data (await it) and return it.
2. In the _authenticated.tsx beforeLoad, check the user data (which comes in as args) for whether someone is authenticated or not.
This makes sense to me
Now I want to also have this data accessible elsewhere. This is why I wanted a UserProvider which you are saying I can just wrap around the root component and inside the provider just store the user data (with another fetch) as react state.
However, I didn't realize that "beforeLoad is executed before each navigation. so if this is not cached, it would call your server for each navigation".
Not sure how to best handle that...
continuing-cyan•11mo ago
use TanStack query for this
as a cache
harsh-harlequinOP•11mo ago
what would the pseudo code in the __root beforeLoad look like?
That's where I'm confused
If going with TSQ^
continuing-cyan•11mo ago
harsh-harlequinOP•11mo ago
I am also on v4 if that matters
continuing-cyan•11mo ago
don't think you need more than that
harsh-harlequinOP•11mo ago
dont think
ensureQueryData is in v4 😓continuing-cyan•11mo ago
QueryClient | TanStack Query Docs
QueryClient The QueryClient can be used to interact with a cache: tsx import { QueryClient } from '@tanstack/react-query' const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime:...
continuing-cyan•11mo ago
it is
harsh-harlequinOP•11mo ago
ok got it!
thank you! Will work with this
automatic-azure•11mo ago
I also want to mention that this should be in
_authenticated/, you dont need anything in __rootcontinuing-cyan•11mo ago
absolutely right
automatic-azure•11mo ago
unless you want to pass the user to the context of routes outside of
_authenticated/continuing-cyan•11mo ago
otherwise you cant login
harsh-harlequinOP•11mo ago
right!
And for logout, would the queryCache invalidation be enough?
continuing-cyan•11mo ago
you would also need to router.invalidate()
harsh-harlequinOP•11mo ago
Or is the router.invalidate needed?
right
continuing-cyan•11mo ago
to force reexecution of beforeLoad
harsh-harlequinOP•11mo ago
sweet
continuing-cyan•11mo ago
and thus redirecting
harsh-harlequinOP•11mo ago
🤞
Quick question - the queryClient should be passed into the router context I'm guessing?
To be accessible in beforeLoad?
continuing-cyan•11mo ago
can be
could also just be imported
only matters if you want to ever inject another instance
e.g. during testing
harsh-harlequinOP•11mo ago
I can't call
const queryClient = useQueryClient(); in the beforeLoad right? How would I import it?continuing-cyan•11mo ago
just import it 🤪
if you have
export queryClient = ... somewhere
you would just import that instance
but using router context is much cleanerharsh-harlequinOP•11mo ago
I see 😅 . I will just go with the router context if that is the better way to do it. I'm guessing that is how you would do it?
continuing-cyan•11mo ago
yes and this is also how we do it in the react query based router examples
harsh-harlequinOP•11mo ago
got it
this all works! thank you so much
just one last (hopefully) follow up - in my _unauthenticated.tsx beforeLoad, I have -
however, this isn't working for some reason... I am still able to see the /login route even when I'm logged in
continuing-cyan•11mo ago
would need a minimal complete example
can you fork an existing one from stackblitz and modify accordingly ?
harsh-harlequinOP•11mo ago
I found the issue. It was quite stupid. I was calling the throw in a try catch and so it wasn't working...
But I'm still unclear on how to handle some patterns
harsh-harlequinOP•11mo ago

harsh-harlequinOP•11mo ago
This is what I'm looking to implement. The thing that is becoming tough to handle is that in my case a
403 error means not logged in vs other errors should have an error component. Since there could be an error thrown in the beforeLoad, I have a try catch. However, I want to also throw redirectautomatic-azure•11mo ago
are you using axios by chance?
harsh-harlequinOP•11mo ago
yes iam
automatic-azure•11mo ago
thought so, as normal fetch does not error on non-200 response codes
so,
403 means you want to throw redirect and anything else should rethrow and be caught by the router's error component, right?harsh-harlequinOP•11mo ago
Ohh
automatic-azure•11mo ago
harsh-harlequinOP•11mo ago
yes correct
But in the _unauthenticated case where there isn't an error, I want to redirect to
/automatic-azure•11mo ago
so wait
you want the user, even in the case where you are not authenticated?
i think we are cycling back to what I said initially lmao
type
user in your router context as User | null
and pass null as default
you could also make it optional, and work with undefined, but I prefer null to signify that it is not there as opposed to, something went wrong and it is not definedharsh-harlequinOP•11mo ago
Also, just in case it wasn't clear I have my
/login route under _unauthenticated.tsx and so if a user is returned, I want it to redirect to /automatic-azure•11mo ago
harsh-harlequinOP•11mo ago
I see what you're doing! You're decoupling the beforeLoads..
And just __root handles the axios GET
automatic-azure•11mo ago
i'd rather do this just in the auth routes tho (login/register etc.), as you might want public routes that logged in users want to access
harsh-harlequinOP•11mo ago
Ya makes sense
Just one question and again this might be stupid
automatic-azure•11mo ago
but, again, make the request using tanstack query, to cache the user and avoid other requests for it
np, ask away
harsh-harlequinOP•11mo ago
In the __root beforeLoad you return
user as User | null. How does the beforeLoad in _authenticated, for example, know the type of the context there?automatic-azure•11mo ago
it's going to show as
User | null
the type wont changeharsh-harlequinOP•11mo ago
yes but how does it infer the type there?
I'm not defining the type explicitly anywhere right?
automatic-azure•11mo ago
it doesnt? you need to specify it when you pass it to
createRootRouteWithContextharsh-harlequinOP•11mo ago
Ah I see
automatic-azure•11mo ago
thats why I told you to type it as
User | null, you give the type for itharsh-harlequinOP•11mo ago
Makes sense
automatic-azure•11mo ago
nothing gets inferred
harsh-harlequinOP•11mo ago
Perfect. All of it makes sense now
I'm curious - does this setup seem wrong?
Or inefficient
automatic-azure•11mo ago
not at all, this is pretty much the recommended way
harsh-harlequinOP•11mo ago
Even how I have my apis set up?
where I do a /user to check for login or not
automatic-azure•11mo ago
that's not odd, usually when using REST apis you'd have something like a
/me API endpoint to give you user profile
which is pretty much same as what you are doingharsh-harlequinOP•11mo ago
yes, makes sense
Dude thanks so much! Just implementing it all now. Hopefully I don't come back to this thread with more Qs lol
automatic-azure•11mo ago
Gl