Home > Blockchain >  Getting error with createBrowserRouter: useNavigate() may be used only in the context of a <Route
Getting error with createBrowserRouter: useNavigate() may be used only in the context of a <Route

Time:11-15

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>
  • Related