I'm trying to switch from BrowserRoute component to createBrowserRouter in React Router 6.4.3 but getting error:
useNavigate() may be used only in the context of a component.
How can I fix that? The error comes from AuthProvider component and I think I need to wrap it with RouterProvider but its not clear how I can do that.
Index.tsx component
const container = document.getElementById('root')!;
const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<AuthProvider>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</AuthProvider>
</Provider>
</React.StrictMode>
);
App.tsx component
const router = createBrowserRouter([
{
element: <ProtectedRoute />,
children: [
{
path: '/',
element: <DashboardLayout />,
children: [
{
index: true,
element: <HomePage />,
},
{
path: 'about',
element: <AboutPage />,
},
],
},
],
},
{
path: '/auth',
element: <Auth />,
},
]);
const App = () => {
const { isLoading } = useAuth0();
if (isLoading) {
return <LoaderPage />;
}
return (
<>
<Box>
<CssBaseline />
<RouterProvider router={router} />
</Box>
</>
);
};
Error comes from AuthProvider.tsx
export const AuthProvider = ({ children }: PropsWithChildren<Auth0ProviderWithConfigProps>): JSX.Element | null => {
const domain = process.env.REACT_APP_AUTH0_DOMAIN;
const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID;
const navigate = useNavigate();
if (!(domain && clientId)) {
return null;
}
const onRedirectCallback = (appState: any) => {
navigate(appState?.returnTo || window.location.pathname);
};
return (
<Auth0Provider
domain={domain}
clientId={clientId}
redirectUri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
{children}
</Auth0Provider>
);
};
UPD
ProtectedRoute.tsx
export const ProtectedRoute: React.FC = () => {
const location = useLocation();
const { isAuthenticated } = useAuth0();
return isAuthenticated? <Outlet /> : <Navigate to="/auth" state={{ from: location }} replace />;
};
If I rollback, change these components to back BrowserRouter then everyting works:
index.tsx
root.render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Provider store={store}>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</Provider>
</AuthProvider>
</BrowserRouter>
</React.StrictMode>
);
App.tsx
const App = () => {
const { isLoading } = useAuth0();
const routes = useRoutes([
{
element: <ProtectedRoute />,
children: [
{
path: '/',
element: <DashboardLayout />,
children: [
{
index: true,
element: <HomePage />,
},
{
path: 'about',
element: <AboutPage />,
},
],
},
],
},
{
path: '/auth',
element: <Auth />,
},
]);
if (isLoading) {
return <LoaderPage />;
}
return (
<>
<Box>
<CssBaseline />
{routes}
</Box>
</>
);
};
Codesandbox: https://codesandbox.io/s/nice-morning-oxwwj6?file=/src/App.tsx
CodePudding user response:
The AuthProvider
component needs to be rendered within the routing context in order to access it and use any of the react-router-dom
hooks. Create a layout route that renders the AuthProvider
wrapping an Outlet
component.
Example:
import {
createBrowserRouter,
Outlet
} from 'react-router-dom';
const AuthLayout = () => (
<AuthProvider>
<Outlet />
</AuthProvider>
);
const router = createBrowserRouter([
{
element: <AuthLayout />,
children: [
{
element: <ProtectedRoute />,
children: [
{
path: '/',
element: <DashboardLayout />,
children: [
{
index: true,
element: <HomePage />,
},
{
path: 'about',
element: <AboutPage />,
},
],
},
],
},
{
path: '/auth',
element: <Auth />,
},
]
},
]);
const App = () => {
const { isLoading } = useAuth0();
if (isLoading) {
return <LoaderPage />;
}
return (
<>
<Box>
<CssBaseline />
<RouterProvider router={router} />
</Box>
</>
);
};
...
const container = document.getElementById('root')!;
const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</Provider>
</React.StrictMode>
);
CodePudding user response:
useNavigation
is only accessible within the routing context.
In this case, auth component is parent to routes. So react can't identify the navigation hook.
So you need to move the auth component inside the routes. Something like this
<Box>
<CssBaseline />
<RouterProvider router={router} >
<AuthProvider />
</RouterProvider>
</Box>