Any big application will have general context that might need the router to redirect user. Since the RouterProvider
can only be use at the root level. How do you use it with application context.
For example I have an ApiContext
that redirect to a 403 route/component if the user does not have the right to access the API.
How to I wrap this context in the RouterProvider
without having to rerender it on each route change. In other word. How can I have parent context that use the router?
It's working fine if I use the BrowserRouter
, but the only way to use any kind of prompt before unload in v6.8 is to use the data api and therefore the createBrowserRouter
method.
The same goes with header navigation. I don't want my header to be in every route component and part of the routing. I want my header to be static and route component to be dynamic.
// Aplication Context
export const ApiContext = React.createContext({});
export const ApiProvider = memo((p: { children: React.ReactNode; }) => {
const navigate = useNavigate();
const [token, setToken] = useState<string>()
if (!token) {
navigate('/403');
}
const value = useMemo(() => ({}), []);
return (
<ApiContext.Provider value={value}>
{p.children}
</ApiContext.Provider>
);
});
// Index
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
children: [
{
path: "403",
element: <Login />,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<ApiProvider>
<Header />
<RouterProvider router={router} />
</ApiProvider>
);
It's not actually factual that the RouterProvider
be the root node/element/component of the app, it just needs to be higher in the ReactTree than any rendered component needing to access the routing context it provides.
If you are rendering components that need access to the routing context provided by a router, e.g. useNavigate
, etc, then they necessarily need to be rendered under the ReactTree created by the router provider, i.e. the router component.
Move the ApiProvider
into the router on a layout route. The ApiProvider
component should now also render an Outlet
for nested routes to render their element
content into instead of a children
prop.
The ApiProvider
component should also not call navigate
as an unintentional side-effect directly in the component body. Either move the if (!token)
check and redirect into a useEffect
hook or just render a redirect with the Navigate
component.
Example:
Application Context
import { Navigate, Outlet } from 'react-router-dom';
export const ApiContext = React.createContext({});
export const ApiProvider = () => {
const [token, setToken] = useState<string>("");
const value = useMemo(() => ({}), []);
if (!token) {
return <Navigate to="/403" replace />;
}
return (
<ApiContext.Provider value={value}>
<Header />
<Outlet />
</ApiContext.Provider>
);
};
Index
const router = createBrowserRouter([
{
element: <ApiProvider />,
children: [
{
path: "/",
element: <Root />,
loader: rootLoader,
children: [
{
path: "team",
element: <Team />,
loader: teamLoader,
},
],
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
);