Router Context (from hooks) #9856
Replies: 15 comments 22 replies
-
@jamesopstad Thanks for the request! This is definitely something on our radar and we've got a few APIs in mind that we're discussing internally. I'll update this issue as we have any more concrete information 👍 |
Beta Was this translation helpful? Give feedback.
-
Hey @brophdawg11! I think this might be a related problem: What are you supposed to do when you have a provider, say ReactDOM.createRoot(document.getElementById("root")).render(
<BrowserRouter>
<AuthProvider>
<App />
</AuthProvider>
</BrowserRouter>
); But not sure what the equivalent would be using v6.4 data APIs. This is not possible, since const router = createBrowserRouter(
createRoutesFromElements(<Route path="/" element={<Root />} />),
);
ReactDOM.createRoot(document.getElementById("root")).render(
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
); This is not possible, since const router = createBrowserRouter(
createRoutesFromElements(
<AuthProvider>
<Route path="/" element={<Root />} />
</AuthProvider>,
),
);
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
); This is possible: const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="/"
element={
<AuthProvider>
<Root />
</AuthProvider>
}
/>,
),
);
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
); But that would require wrapping Any suggestions? |
Beta Was this translation helpful? Give feedback.
-
Just use a pathless const router = createBrowserRouter(
createRoutesFromElements(
<Route element={<AuthProvider />}>
<Route path="/" element={<Root />} />
{/* More Routes here... */}
</Route>
)
) |
Beta Was this translation helpful? Give feedback.
-
Easy as that! Thanks! |
Beta Was this translation helpful? Give feedback.
-
I've been test driving the new data loading apis, and I like the concept but in practice I'm finding it challenging to integrate with. It feels like a feature like this might help. The use case is simple. I've got an api that uses JWT auth. What's the best way to pass the current access token (and dealing with updating it etc) into a loader. The loaders are more or less pure functions it seems. The only example I see around auth, doesn't use the Maybe there's a way to do it, but wasn't clear through my investigation this morning. For a bit more context. I'm currently using the My ideal scenario would be to be able to provide/update the access token that can be used by the data loaders for accessing protected data. Interested to see / hear about what new api's might be in the works for the data loading side. |
Beta Was this translation helpful? Give feedback.
-
@timdorr I tried your option, it only works fine, when you click around the site, but when I hit refresh, or enter direct page, the loader always crashes, since msalInstance is not initialised yet. |
Beta Was this translation helpful? Give feedback.
-
While this feature is being considered, I'd like to add that it'd be useful to provide a mechanism for passing values from hooks into loaders and actions. As it stands, Auth0's React SDK library only allows token values to be accessed via the |
Beta Was this translation helpful? Give feedback.
-
+1. I'd really like to migrate my app to Remix (or at least react-router 6.4), but I use custom Hooks to fetch all of my data. These Hooks use an export const useGetUser = async (id: string) => {
const { apiRequest } = useSession();
return await apiRequest({
path: `identity/user/${id}`,
method: "GET",
})
}; I can't call these inside a loader. The best I can do is wrap them in Tanstack Query, and use the workaround mentioned by the OP here. Not ideal. Because the session is stored inside of React context, the solution proposed in the OP isn't quite right for me either. In a perfect world, I could do something like this: // App.tsx
const App = () => {
<SessionProvider>
{(sessionContext) => (
<RouterProvider context={{ sessionContext }}/>
)}
</SessionProvider>
} and my loader could look like this: // User.tsx
export const loader = async ({ params }, context) => {
const { apiRequest } = context.sessionContext
return apiRequest({
path: `identity/user/${id}`,
method: "GET",
})
} Am I missing any workarounds? Would love some pointers if I'm going about this the wrong way. |
Beta Was this translation helpful? Give feedback.
This comment has been minimized.
This comment has been minimized.
-
I would like to see some type of official context api added as well. Here is what I'm currently doing to ensure my loaders and actions have access to the firebase services. It would be great to remove the import { LoaderFunctionArgs, ActionFunctionArgs } from "react-router-dom"
declare module "react-router-dom" {
interface LoaderFunctionArgs {
context: IFirebaseContext
}
interface ActionFunctionArgs {
context: IFirebaseContext
}
} import AppShell from "@components/AppShell"
import Loader from "@components/Loader"
import { ROUTES } from "@constants"
import { useFirebase } from "@Firebase"
import DashboardRoute, { DashboardLoader } from "@routes/dashboard"
import { createBrowserRouter, Navigate, RouterProvider } from "react-router-dom"
export const AppRouter = () => {
const { loading, ...context } = useFirebase()
// Wait for auth to finish loading
if (loading) {
return <Loader />
}
return (
<>
<RouterProvider
router={createBrowserRouter([
{
element: <AppShell />,
children: [
{
index: true,
path: ROUTES.Dashboard.slug,
element: <DashboardRoute />,
loader: (args) => DashboardLoader({ ...args, context }),
},
],
},
{
path: "*",
element: <Navigate to={ROUTES.Dashboard.path} />,
},
])}
/>
</>
)
}
export default AppRouter |
Beta Was this translation helpful? Give feedback.
-
There's a Generally speaking though - we don't want React Context to be a dependency for data fetching - since the entire idea of the 6.4 data APIs is to decouple data fetching from rendering. In order to access client side data from a react context, it has to be handed off to a context provider somewhere higher up in the tree and by definition is then accessible from JS somehow. The solution is then to access that data from the source in JS, and not from context. But not all third party APIs currently give you easy access to some of this stuff in a non-context manner. |
Beta Was this translation helpful? Give feedback.
-
hey, is this feature already exist? i see that react-router passing { params, request, context } to loader function. but i dont understand how to set this context. |
Beta Was this translation helpful? Give feedback.
-
My use case is Firebase Authentication. In my React app, I created a context so every component using the user will be re-rendered when user is loaded, signed in, or signed out. I cannot find a way to implement this without context. const UserContext = createContext<User | null | undefined>(undefined);
export const UserContextProvider: React.FC<{ children: ReactNode }> = props => {
const [user, setUser] = useState<User | null>();
useEffect(() => {
return auth.onAuthStateChanged(setUser);
}, []);
return (
<UserContext.Provider value={user}>{props.children}</UserContext.Provider>
);
};
/**
* Get the current Firebase user.
* @returns a `User` if the user is signed in, `null` if the user it not signed in,
* or `undefined` if we're working on getting it or if `UserContext` is not provided.
*/
export function useUser(): User | null | undefined {
return useContext(UserContext);
} |
Beta Was this translation helpful? Give feedback.
-
I don't quite understand what the issue is with the original poster's suggested solution? It unifies the behavior between the static / server router, and the client router, so that we can have isomorphic loaders that don't need to care which environment they're running in. In react-query use case, on the server the client needs to be created per request and passed in (can't do singleton), while in the client entry we can create a singleton pass that in to the browser router's loader context. I've seen a lot of posts mentioning that we don't want to rely on react context, and want data fetching and rendering to be separated. I agree! ... however, how does the original poster's solution not satisfy these two key concerns? React context is not involved, nor is rendering. |
Beta Was this translation helpful? Give feedback.
-
Can someone clarify whether #9564 or remix-run/remix#7645 would enable using (the result of) the same hooks we use elsewhere inside our loaders? I frequently use hooks for retrieving query parameters (with some post processing for defaults etc), but I then need to add a lot of complexity to also make this work inside loaders. (A big part of this is probably caused by React router not treating query parameters as a first class citizen) |
Beta Was this translation helpful? Give feedback.
-
What is the new or updated feature that you are suggesting?
It would be useful to be able to set a
context
value when initialising the router that can then be accessed inloaders
andactions
.Example
Why should this feature be included?
As
loaders
andactions
cannot use React hooks, there is currently no way to access contextual data within them. A workaround suggested in this article (https://tkdodo.eu/blog/react-query-meets-react-router) is to create an additional function wrapper for eachloader
andaction
. Providing acontext
value directly when initialising the router would be a more elegant solution.Beta Was this translation helpful? Give feedback.
All reactions