Я пытаюсь реализовать JWT в своем проекте, и статья предлагает хорошее решение:
https://dev.to/sanjayttg/jwt-authentication-in-react-with-react-router-1d03.
Маршруты.jsx
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";
import Login from "../pages/Login";
import Logout from "../pages/Logout";
const Routes = () => {
const { token } = useAuth();
// Define public routes accessible to all users
const routesForPublic = [
{
path: "/service",
element: <div>Service Page</div>,
},
{
path: "/about-us",
element: <div>About Us</div>,
},
];
// Define routes accessible only to authenticated users
const routesForAuthenticatedOnly = [
{
path: "/",
element: <ProtectedRoute />, // Wrap the component in ProtectedRoute
children: [
{
path: "",
element: <div>User Home Page</div>,
},
{
path: "/profile",
element: <div>User Profile</div>,
},
{
path: "/logout",
element: <Logout/>,
},
],
},
];
// Define routes accessible only to non-authenticated users
const routesForNotAuthenticatedOnly = [
{
path: "/",
element: <div>Home Page</div>,
},
{
path: "/login",
element: <Login/>,
},
];
// Combine and conditionally include routes based on authentication status
const router = createBrowserRouter([
...routesForPublic,
...(!token ? routesForNotAuthenticatedOnly : []),
...routesForAuthenticatedOnly,
]);
// Provide the router configuration using RouterProvider
return <RouterProvider router = {router} />;
};
export default Routes;
ProtectedRoute.jsx
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
export const ProtectedRoute = () => {
const { token } = useAuth();
// Check if the user is authenticated
if (!token) {
// If not authenticated, redirect to the login page
return <Navigate to = "/login" />;
}
// If authenticated, render the child routes
return <Outlet />;
};
Гитхаб: https://github.com/sanjay-arya/react-auth-demo
По сути, я создал контекст аутентификации, предоставляющий информацию о токене для всех маршрутов и разрешающий доступ к маршрутам в зависимости от того, есть ли у пользователя токен или нет. Он также имеет систему входа и выхода из системы, удаляющую/создающую токены.
Предоставленный код работает нормально и так. Проблема возникает, когда я хочу перенаправить пользователя на страницу "/login"
вместо страницы "/"
после выхода из системы. например Я изменил navigate("/", { replace: true })
на navigate("/login", { replace: true });
в Logout.jsx.
Я получаю эту ошибку:
react-router-dom.js?v=7652cf65:202 Uncaught Error: Could not find a matching route for errors on route IDs: 2
at invariant (react-router-dom.js?v=7652cf65:202:11)
at _renderMatches (react-router-dom.js?v=7652cf65:3167:33)
at useRoutesImpl (react-router-dom.js?v=7652cf65:3033:25)
at DataRoutes (react-router-dom.js?v=7652cf65:3422:10)
at renderWithHooks (react-dom_client.js?v=7652cf65:12171:26)
at updateFunctionComponent (react-dom_client.js?v=7652cf65:14577:28)
at beginWork (react-dom_client.js?v=7652cf65:15912:22)
at HTMLUnknownElement.callCallback2 (react-dom_client.js?v=7652cf65:3674:22)
at Object.invokeGuardedCallbackDev (react-dom_client.js?v=7652cf65:3699:24)
at invokeGuardedCallback (react-dom_client.js?v=7652cf65:3733:39)
Show 19 more frames
console.js:213 The above error occurred in the <DataRoutes> component:
at DataRoutes (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=7652cf65:3419:5)
at Router (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=7652cf65:3491:15)
at RouterProvider (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=7652cf65:3375:5)
at Routes (http://localhost:5173/src/routes/index.jsx?t=1712483351401:26:7)
at AuthProvider (http://localhost:5173/src/provider/authProvider.jsx:21:3)
at App
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
Это необнаруженная ошибка, но даже при добавлении errorElement в маршруты ошибки все равно сохраняются. Даже тогда я до сих пор не знаю, почему может возникнуть ошибка. Я пытался разобраться в этой проблеме часами, но безуспешно.
Общая проблема заключается в том, что вы условно отображаете свои маршруты, поэтому, когда условие аутентификации на основе token
меняется, некоторые маршруты, по которым вы хотите перейти, еще не монтируются и не отображаются. Решение состоит в том, чтобы безоговорочно отображать маршруты.
Обновите Routes
, чтобы удалить проверку token
и безоговорочно визуализировать routesForNotAuthenticatedOnly
маршруты, а также создайте «компонент защищенного маршрута», который выполняет обратную функцию ProtectedRoute
, чтобы защитить эти маршруты от аутентифицированных пользователей.
Поскольку на корневом маршруте "/"
есть «защищенная» домашняя страница пользователя и «анонимная» домашняя страница, создайте специальный компонент, который проверяет условие аутентификации и условно отображает любую из домашних страниц в общедоступных маршрутах, доступных любому. в любое время.
Пример:
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
export const AnonymousRoute = () => {
const { token } = useAuth();
// Check if the user is authenticated
if (!token) {
// If not authenticated, render the child routes
return <Outlet />;
}
// If authenticated, redirect to safe page
return <Navigate to = "/" />;
};
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { useAuth } from "../provider/authProvider";
import { ProtectedRoute } from "./ProtectedRoute";
import { AnonymousRoute } from "./ProtectedRoute";
import Login from "../pages/Login";
import Logout from "../pages/Logout";
const HomePage = () => {
const { token } = useAuth();
return token
? <div>User Home Page</div>
: <div>Home Page</div>;
};
const Routes = () => {
// Define public routes accessible to all users
const routesForPublic = [
{
path: "/",
element: <HomePage />,
},
{
path: "/service",
element: <div>Service Page</div>,
},
{
path: "/about-us",
element: <div>About Us</div>,
},
];
// Define routes accessible only to authenticated users
const routesForAuthenticatedOnly = [
{
element: <ProtectedRoute />,
children: [
{
path: "/profile",
element: <div>User Profile</div>,
},
{
path: "/logout",
element: <Logout />,
},
],
},
];
// Define routes accessible only to non-authenticated users
const routesForNotAuthenticatedOnly = [
{
element: <AnonymousRoute />,
children: [
{
path: "/login",
element: <Login />,
},
],
},
];
// Combine and conditionally include routes based on authentication status
const router = createBrowserRouter([
...routesForPublic,
...routesForNotAuthenticatedOnly),
...routesForAuthenticatedOnly,
]);
// Provide the router configuration using RouterProvider
return <RouterProvider router = {router} />;
};
export default Routes;
@ byles1506 Это связано с тем, что обновления состояния React обрабатываются асинхронно, а действия навигации выполняются немедленно. Думайте о любой из функций React setState
как о «React при ближайшем удобном случае, пожалуйста, обновите это состояние до этого значения», и React помещает обновление в очередь для обработки между циклами рендеринга. Надеюсь, эта аналогия имеет смысл.
@ byles1506 Альтернативой является размещение вызова navigate
в setTimeout
, чтобы он помещался в конец очереди событий Javascript и вызывался после обработки всех поставленных в очередь обновлений состояния React. Однако это своего рода хитрость, и ее не рекомендуется использовать, если вам подходят обычные средства, предложенные в ответе.
Спасибо! Но когда вы говорите: «Некоторые маршруты, по которым вы хотите перейти, еще не смонтированы и не отображены», я изначально думал, что для
const handleLogout = () => { setToken(); navigate("/", { replace: true }); };
setToken() сначала переотобразит компонент<Routes>
и смонтирует правильный маршрутизатор, т. е. тот, который включаетroutesForNotAuthenticatedOnly
перед навигация. Почему обновленный маршрутизатор еще не отрендерен и не смонтирован?