React登录成功无法跳转至Admin页面,报Route使用错误
React 路由权限校验错误问题排查
开发React应用时,访问/admin页面会校验用户登录状态,未登录则重定向至/login页面。通过后端请求完成登录并生成token后,跳转至/admin页面时出现错误,相关代码及错误信息如下:
router/index.jsx
import { createBrowserRouter } from "react-router-dom"; import WebLayout from "~/../layouts/web"; import AdminLayout from "~/../layouts/admin"; import Home from "~/pages/web/home"; import Orders from "~/pages/admin/orders"; import Categories from "~/pages/admin/categories"; import Tables from "~/pages/admin/tables"; import Meals from "~/pages/admin/meals"; import AdminHome from "~/pages/admin/home"; import MealDetail from "~/pages/web/mealdetail"; import Login from "~/components/shared/login"; import PrivateRoute from "~/components/shared/privateroute"; const routes = createBrowserRouter([ { path: "/login", element: <Login />, }, { path: "/", element: <WebLayout />, children: [ { index: true, element: <Home />, }, { path: "meal/:mealId", element: <MealDetail />, }, ], }, { path: "/admin", element: <AdminLayout />, children: [ { index: true, element: <PrivateRoute component={AdminHome} />, }, { path: "orders", element: <PrivateRoute component={Orders} />, }, { path: "categories", element: <PrivateRoute component={Categories} />, }, { path: "tables", element: <PrivateRoute component={Tables} />, }, { path: "meals", element: <PrivateRoute component={Meals} />, }, ], }, ]); export default routes;
privateroute/index.jsx
import React from "react"; import { Route, Navigate } from "react-router-dom"; import { useSelector } from "react-redux"; const PrivateRoute = ({ element: Element, ...rest }) => { const isAuthenticated = useSelector((state) => state.auth.isAuthenticated); return isAuthenticated ? ( <Route {...rest} element={<Element />} /> ) : ( <Navigate to="/login" /> ); }; export default PrivateRoute;
login/index.jsx
import React, { useState } from "react"; import { useDispatch } from "react-redux"; import { loginAsync } from "~/store/authSlice"; import { useNavigate } from "react-router-dom"; const Login = () => { const dispatch = useDispatch(); const navigate = useNavigate(); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const handleLogin = async () => { try { await dispatch(loginAsync({ username, password })); navigate("/admin"); } catch (error) { console.error("Login error:", error.message); } }; return ( <div> <input type="text" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} /> <input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button onClick={handleLogin}>Login</button> </div> ); }; export default Login;
authslice.js
// src/store/authSlice.js import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import axios from 'axios'; const initialState = { token: null, username: null, isAuthenticated: false, }; export const loginAsync = createAsyncThunk('auth/login', async ({ username, password }) => { try { const response = await axios.post('http://localhost:3000/api/login', { username, password, }); console.log(response.data); const data = response.data; return { token: data.token, username }; } catch (error) { throw new Error(error.response.data.error); } }); const authSlice = createSlice({ name: 'auth', initialState, reducers: { logout(state) { state.token = null; state.username = null; state.isAuthenticated = false; }, }, extraReducers: (builder) => { builder.addCase(loginAsync.fulfilled, (state, action) => { const { token, username } = action.payload; state.token = token; state.username = username; state.isAuthenticated = true; }); }, }); export const { logout } = authSlice.actions; export default authSlice.reducer;
错误信息
Unexpected Application Error! A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>. Error: A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>. at invariant (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=9572ed87:202:11) at Route (http://localhost:5173/node_modules/.vite/deps/react-router-dom.js?v=9572ed87:3706:10) at renderWithHooks (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:12171:26) at mountIndeterminateComponent (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:14921:21) at beginWork (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:15902:22) at beginWork$1 (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19749:22) at performUnitOfWork (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19194:20) at workLoopSync (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19133:13) at renderRootSync (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:19112:15) at recoverFromConcurrentError (http://localhost:5173/node_modules/.vite/deps/chunk-UHLQBSTO.js?v=9572ed87:18732:28) 💿 Hey developer 👋 You can provide a way better UX than this when your app throws errors by providing your own ErrorBoundary or errorElement prop on your route.
问题解决方法
错误根源是React Router v6中,createBrowserRouter采用配置式路由定义,<Route>组件不能直接作为普通组件渲染,必须作为路由配置的一部分。
1. 修改PrivateRoute组件
将其改为权限校验的包裹组件,直接渲染目标组件或重定向:
import React from "react"; import { Navigate } from "react-router-dom"; import { useSelector } from "react-redux"; const PrivateRoute = ({ children }) => { const isAuthenticated = useSelector((state) => state.auth.isAuthenticated); return isAuthenticated ? children : <Navigate to="/login" replace />; }; export default PrivateRoute;
2. 更新路由配置
在路由的element中用PrivateRoute包裹页面组件:
// router/index.jsx 中admin子路由部分 { path: "/admin", element: <AdminLayout />, children: [ { index: true, element: <PrivateRoute><AdminHome /></PrivateRoute>, }, { path: "orders", element: <PrivateRoute><Orders /></PrivateRoute>, }, { path: "categories", element: <PrivateRoute><Categories /></PrivateRoute>, }, { path: "tables", element: <PrivateRoute><Tables /></PrivateRoute>, }, { path: "meals", element: <PrivateRoute><Meals /></PrivateRoute>, }, ], },
修改完成后,权限校验逻辑正常运行,不会再出现<Route>直接渲染的错误。
内容的提问来源于stack exchange,提问作者İsmail Cem Altekin
相关产品推荐
相关产品推荐

