reactjs - Får fel med createBrowserRouter: useNavigate() får endast användas i kontexten av en <Router>-komponent


Jag försöker byta från BrowserRoute-komponenten till createBrowserRouter i React Router 6.4.3 men får ett fel:

useNavigate() får endast användas i kontexten av en komponent.

Hur kan jag åtgärda det? Felet kommer från AuthProvider-komponenten och jag tror att jag behöver omsluta den med RouterProvider, men det är inte klart hur jag kan göra det.

Index.tsx-komponent

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-komponent

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>
    </>
  );
};

Fel kommer från 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 />;
};

Om jag återställer, ändrar dessa komponenter tillbaka till BrowserRouter så fungerar allt:

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

Svar #1

Komponenten AuthProvider behöver renderas inom routing-kontexten för att kunna nå den och använda någon av react-router-dom-hookarna. Skapa en layout-rutt som renderar AuthProvider och omsluter en Outlet-komponent.

Exempel :

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>
);

Relaterade artiklar