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
vicious-gold•8mo 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 beforeLoad
optimistic-goldOP•8mo 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?vicious-gold•8mo ago
router.invalidate()
other-emerald•8mo ago
you can supply hook values via the routerprovider
<RouterProvider context={{foo: 'bar'}>
optimistic-goldOP•8mo 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?other-emerald•8mo 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
optimistic-goldOP•8mo 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!
other-emerald•8mo 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)
optimistic-goldOP•8mo 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?
other-emerald•8mo ago
not necessarily
I would go for (2)
optimistic-goldOP•8mo ago
Right. I guess you're suggesting using the router context instead of my own user context here in (2)?
vicious-gold•8mo ago
Why this when he could pass the promise resulting from
fetch()
?other-emerald•8mo 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
)optimistic-goldOP•8mo ago
right that makes sense now
other-emerald•8mo 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.optimistic-goldOP•8mo ago
I got a bit confused there... sorry
https://discord.com/channels/719702312431386674/1330884061144551427/1330986250642919556 could you first explain how this would be possible?
other-emerald•8mo ago
where is this pointing to?
optimistic-goldOP•8mo 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...
other-emerald•8mo ago
use TanStack query for this
as a cache
optimistic-goldOP•8mo ago
what would the pseudo code in the __root beforeLoad look like?
That's where I'm confused
If going with TSQ^
other-emerald•8mo ago
optimistic-goldOP•8mo ago
I am also on v4 if that matters
other-emerald•8mo ago
don't think you need more than that
optimistic-goldOP•8mo ago
dont think
ensureQueryData
is in v4 😓other-emerald•8mo 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:...
other-emerald•8mo ago
it is
optimistic-goldOP•8mo ago
ok got it!
thank you! Will work with this
vicious-gold•8mo ago
I also want to mention that this should be in
_authenticated/
, you dont need anything in __root
other-emerald•8mo ago
absolutely right
vicious-gold•8mo ago
unless you want to pass the user to the context of routes outside of
_authenticated/
other-emerald•8mo ago
otherwise you cant login
optimistic-goldOP•8mo ago
right!
And for logout, would the queryCache invalidation be enough?
other-emerald•8mo ago
you would also need to router.invalidate()
optimistic-goldOP•8mo ago
Or is the router.invalidate needed?
right
other-emerald•8mo ago
to force reexecution of beforeLoad
optimistic-goldOP•8mo ago
sweet
other-emerald•8mo ago
and thus redirecting
optimistic-goldOP•8mo ago
🤞
Quick question - the queryClient should be passed into the router context I'm guessing?
To be accessible in beforeLoad?
other-emerald•8mo ago
can be
could also just be imported
only matters if you want to ever inject another instance
e.g. during testing
optimistic-goldOP•8mo ago
I can't call
const queryClient = useQueryClient();
in the beforeLoad
right? How would I import it?other-emerald•8mo ago
just import it 🤪
if you have
export queryClient = ...
somewhere
you would just import that instance
but using router context is much cleaneroptimistic-goldOP•8mo 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?
other-emerald•8mo ago
yes and this is also how we do it in the react query based router examples
optimistic-goldOP•8mo 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
other-emerald•8mo ago
would need a minimal complete example
can you fork an existing one from stackblitz and modify accordingly ?
optimistic-goldOP•8mo 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
optimistic-goldOP•8mo ago

optimistic-goldOP•8mo 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 redirect
vicious-gold•8mo ago
are you using axios by chance?
optimistic-goldOP•8mo ago
yes iam
vicious-gold•8mo 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?optimistic-goldOP•8mo ago
Ohh
vicious-gold•8mo ago
optimistic-goldOP•8mo ago
yes correct
But in the _unauthenticated case where there isn't an error, I want to redirect to
/
vicious-gold•8mo 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 definedoptimistic-goldOP•8mo 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 /
vicious-gold•8mo ago
optimistic-goldOP•8mo ago
I see what you're doing! You're decoupling the beforeLoads..
And just __root handles the axios GET
vicious-gold•8mo 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
optimistic-goldOP•8mo ago
Ya makes sense
Just one question and again this might be stupid
vicious-gold•8mo ago
but, again, make the request using tanstack query, to cache the user and avoid other requests for it
np, ask away
optimistic-goldOP•8mo 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?vicious-gold•8mo ago
it's going to show as
User | null
the type wont changeoptimistic-goldOP•8mo ago
yes but how does it infer the type there?
I'm not defining the type explicitly anywhere right?
vicious-gold•8mo ago
it doesnt? you need to specify it when you pass it to
createRootRouteWithContext
optimistic-goldOP•8mo ago
Ah I see
vicious-gold•8mo ago
thats why I told you to type it as
User | null
, you give the type for itoptimistic-goldOP•8mo ago
Makes sense
vicious-gold•8mo ago
nothing gets inferred
optimistic-goldOP•8mo ago
Perfect. All of it makes sense now
I'm curious - does this setup seem wrong?
Or inefficient
vicious-gold•8mo ago
not at all, this is pretty much the recommended way
optimistic-goldOP•8mo ago
Even how I have my apis set up?
where I do a /user to check for login or not
vicious-gold•8mo 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 doingoptimistic-goldOP•8mo 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
vicious-gold•8mo ago
Gl