You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 08:35:44