T
TanStack2y ago
optimistic-gold

Router context for non-root routes

Here is the problem I am facing, I have a provider that wraps a child route, for other routes nested in this child route, I need to attach some context from this provider in my query call. How can I access the context from this provider on a route loader? Thanks
No description
23 Replies
optimistic-gold
optimistic-goldOP2y ago
is there way to create a child route with context? seems like only root route can be attached a context
other-emerald
other-emerald2y ago
You'll need to set the ContextProvider above the Router, and pass its values into the Router's Context.
const App = () => {
return <ContextProvider><InnerApp /></ContextProvider>
}
const InnerApp = () => {
const workspace = useWorkspace();
return <RouterProvider router={router} context={{ workspace }} />
}
const App = () => {
return <ContextProvider><InnerApp /></ContextProvider>
}
const InnerApp = () => {
const workspace = useWorkspace();
return <RouterProvider router={router} context={{ workspace }} />
}
Then you can access it from the Router context.
optimistic-gold
optimistic-goldOP2y ago
Aha, here is the problem, my workspace context takes a path param as input
optimistic-gold
optimistic-goldOP2y ago
No description
optimistic-gold
optimistic-goldOP2y ago
This is why I cannot not wrap WorkspaceProvider like you showed it depends on the $namespace param on the path btw thanks for the answering! Really appreciate your time :)
other-emerald
other-emerald2y ago
You could simplify the setting of the ContextProvider is you have the following file?
src/routes/_protected/workspace/route.tsx
// or
src/routes/_protected/workspace/route.lazy.tsx
src/routes/_protected/workspace/route.tsx
// or
src/routes/_protected/workspace/route.lazy.tsx
Then you could hoist the ContextProvider over there which'll wrap all the child routes in the RouteProvider. The issue is that you can't inject anything else into the Router's Context since the RouterProvider is so much higher in the tree. What is the requirement of pulling data out of the useWorkspace hook?
optimistic-gold
optimistic-goldOP2y ago
basically the workspace provider takes in the namespace, will fetch the workspaceByNamespace, this will give the workspaceID, and workspaceID will be used for subsequent queries. its kinda like this namespace -> workspaceId conversion makes the URL has a nice and humanreadable namespace instead of a long workspaceID in it
other-emerald
other-emerald2y ago
You could pass that down using the beforeLoad function into the children routes by returning that in context. You could form your workspace values in the /_protected/workspace route and return that in context. That'll provide access to the values below like so:
// /_protected/workspace/route.tsx
export const Route = createFileRoute("/_protected/workspace")({
beforeLoad: async () => {
const workspace = await doSomething();
return { workspace }
}
})

// /_protected/workspace/child-route.tsx
export const Route = createFileRoute("/_protected/workspace")({
beforeLoad: async ({ context: { workspace } }) => {
// workspace value has the previously gotten values
}
})
// /_protected/workspace/route.tsx
export const Route = createFileRoute("/_protected/workspace")({
beforeLoad: async () => {
const workspace = await doSomething();
return { workspace }
}
})

// /_protected/workspace/child-route.tsx
export const Route = createFileRoute("/_protected/workspace")({
beforeLoad: async ({ context: { workspace } }) => {
// workspace value has the previously gotten values
}
})
It will however, not be interacting with React context; You could even seed this data into React context by calling the useRouteContext hook.
optimistic-gold
optimistic-goldOP2y ago
I see, interesting, this could be a solution, tho would this trigger too many fetches? since every route that needs workspaceId will essentially have to do a separate fetch
other-emerald
other-emerald2y ago
In your original screenshot you were using React-Query yes? That'll handle caching.
// /_protected/workspace/route.tsx
export const Route = createFileRoute("/_protected/workspace")({
beforeLoad: async ({ context: { queryClient } }) => {
const queryOpts = { ... };
const exists = await queryClient.ensureQueryData(queryOpts)
const workspace = queryClient.getQueryData(queryOpts.queryKey);
if (!workspace) {
// maybe an error occurred or workspace doesn't exists and you can throw an appropriate error
}
return { workspace }
}
})
// /_protected/workspace/route.tsx
export const Route = createFileRoute("/_protected/workspace")({
beforeLoad: async ({ context: { queryClient } }) => {
const queryOpts = { ... };
const exists = await queryClient.ensureQueryData(queryOpts)
const workspace = queryClient.getQueryData(queryOpts.queryKey);
if (!workspace) {
// maybe an error occurred or workspace doesn't exists and you can throw an appropriate error
}
return { workspace }
}
})
Since react-query will be handling the actual triggering of the data fetching, it'll be in your control. ensureQueryData won't do anything if the cached data is active/isn't stale.
optimistic-gold
optimistic-goldOP2y ago
oh wow, very interesting. basically for every route that wants this "extra" workspace context, I will just throw a beforeLoad like this with react query. am I understanding this correctly?
other-emerald
other-emerald2y ago
Not every route, you can do this at the parent level in a route.tsx file, and return it from the beforeLoad callback. Then all child routes will have access to whatever you returned in the parent's beforeLoad. Since the router goes top down, so first rootRoute is resolved, then the parentRoute, then the childRoute. You should only have to do this once.
optimistic-gold
optimistic-goldOP2y ago
ohhh! perfect, so the beforeLoad propagates to all child route
other-emerald
other-emerald2y ago
Yup yup yup
optimistic-gold
optimistic-goldOP2y ago
wow amazing, thank you so much man! this really helped a lot
other-emerald
other-emerald2y ago
*the return of the beforeLoad
optimistic-gold
optimistic-goldOP2y ago
cool! much appreciated!
other-emerald
other-emerald2y ago
Cool stuff. Please mark as resolved/solved if everything is all good.
optimistic-gold
optimistic-goldOP2y ago
perfect, how can I mark it as resolved?
other-emerald
other-emerald2y ago
Should be a green tick mark next to the thumbs up at the bottom of your original post I think.
optimistic-gold
optimistic-goldOP2y ago
ah got it, done :5636_100000:
optimistic-gold
optimistic-goldOP2y ago
@Sean Cassiere Hey Sean, beforeLoad worked like a charm. Though I have a followup question, I have this loader that returns a bunch of account, if I mutate the account later, how can I get tanstack-router to reload?
No description
optimistic-gold
optimistic-goldOP2y ago
I tried queryClient.invalidateQueries, but it does not seem to work nvm! I forgot to use useSuspenseQuery and loaded it from useLoaderDAta instead, oops

Did you find this page helpful?