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

React Router v6+Vite+TS 受保护路由刷新跳转问题求助

React Router DOM v6 受保护路由问题排查与修复

我在React Router DOM v6中开发受保护路由时遇到两个核心问题:

  • 页面刷新曾出现空白,修复后刷新特定页面会直接跳转到首页
  • 尝试过createBrowserRouter等多种方案,问题仍未解决

路由配置文件

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HomePage from "../pages/HomePage";
import Login from "../sign/Admin/Login";
import Signup from "../sign/Admin/Signup";
import { RootState } from "../../store";
import { useSelector } from "react-redux";
import CategoryPage from "../pages/CategoryPage";
import StorePage from "../pages/StorePage";
import ProductPage from "../pages/ProductPage";
import ServicePage from "../pages/ServicePage";
import UserPage from "../pages/UserPage";
import AdminPage from "../pages/AdminPage";
import OrderPage from "../pages/OrderPage";
import PrivateRoutes from "./PrivateRoutes";

const CustomeRoutes: React.FC = () => {
// const { isLoading, error, errorMessage } = useSelector((state: RootState) => state.admin);

return (
  <>
    <Router>
      <Routes>
        <Route element={<PrivateRoutes />}>
          <Route element={<HomePage />} path="/" />
          <Route path="/categories" element={<CategoryPage />} />
          <Route path="/stores" element={<StorePage />} />
          <Route path="/products" element={<ProductPage />} />
          <Route path="/services" element={<ServicePage />} />
          <Route path="/users" element={<UserPage />} />
          <Route path="/admins" element={<AdminPage />} />
          <Route path="/orders" element={<OrderPage />} />
        </Route>
        <Route path="/login" element={<Login />} />
        <Route path="/signup" element={<Signup />} />
      </Routes>
    </Router>
  </>
);
};

export default CustomeRoutes;

App.tsx

import useLocalizeDocumentAttributes from "./i18n/useLocalizeDocumentAttributes";
import { AppDispatch, RootState } from "./store";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { getAdminByToken } from "./store/slices/admin";
import CutomeRoutes from "./components/routes";

function App() {
  useLocalizeDocumentAttributes();
  const dispatch = useDispatch<AppDispatch>();
  // const { isLoading, error, errorMessage } = useSelector((state: RootState) => state.admin);

  useEffect(() => {
    const tokenFromStorage = localStorage.getItem("user-token");
    if (tokenFromStorage) {
      dispatch(getAdminByToken(tokenFromStorage));
    }
  }, [dispatch]);

  return (
    <>
      <CutomeRoutes />
    </>
  );
}

export default App;

PrivateRoutes组件

const PrivateRoutes = () => {
  const { token, isLoading } = useSelector((state: RootState) => state.admin);
  console.log("PrivateRoutes=>", { token });
  React.useEffect(() => {}, [token]);
  //   if (isLoading) return <div>Loading...</div>;

  return !!token ? <Outlet /> : <Navigate to="/login" />;
};

登录处理逻辑

const onSubmit: SubmitHandler<FormFields> = async (inputData, event) => {
    try {
      console.log(inputData);
      if (loginKey) {
        // If loginKey exists, dispatch acceptLoginKey action
        dispatch(acceptAdminLoginKey({ phone: inputData.phoneNumber, loginKey: parseInt(inputData.loginKey) }));
      } else {
        dispatch(loginAdmin(inputData.phoneNumber));
      }
    } catch (error) {
      console.log(error);
      setError("root", {
        message: "There's something wrong " + error,
      });
    }
  };
  useEffect(() => {
  
    if (token) {
      const { from } = location.state || { from: { pathname: "/" } };
      navigate(from, { replace: true });
    }
  }, [token]);

问题根源与修复方案

1. 核心问题分析

  • 刷新跳转首页:PrivateRoutes未处理isLoading状态,页面刷新时getAdminByToken请求还在进行,此时token为undefined,直接触发跳转到登录页;登录逻辑中默认跳转路径为/,导致后续拿到token后跳转到首页而非当前刷新的页面。
  • 曾出现的空白问题:加载状态缺失导致验证未完成时无内容渲染,或路由Outlet使用不规范。

2. 具体修复步骤

完善PrivateRoutes的状态处理

修改PrivateRoutes,在验证过程中展示加载界面,并记录当前跳转路径:

import { Outlet, Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { RootState } from "../../store";

const PrivateRoutes = () => {
  const { token, isLoading } = useSelector((state: RootState) => state.admin);

  // 加载中时显示加载组件,避免错误跳转
  if (isLoading) {
    return <div>加载中...</div>; // 可替换为项目全局加载组件
  }

  // 已登录则渲染子路由,否则跳转到登录页并记录当前路径
  return !!token ? (
    <Outlet />
  ) : (
    <Navigate to="/login" state={{ from: window.location.pathname }} replace />
  );
};

export default PrivateRoutes;

修正登录后的跳转逻辑

确保登录成功后跳转到之前记录的路径:

// 登录组件中的useEffect
useEffect(() => {
  if (token) {
    // 优先使用跳转前的路径,默认才跳转到首页
    const { from } = location.state || { from: { pathname: "/" } };
    navigate(from, { replace: true });
  }
}, [token, navigate, location.state]);

确保Redux异步状态正确更新

检查getAdminByToken的Redux Slice逻辑,确保isLoading状态在请求开始/结束时正确切换:

// 示例Redux Slice中的异步处理逻辑
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

export const getAdminByToken = createAsyncThunk(
  "admin/getAdminByToken",
  async (token: string, { rejectWithValue }) => {
    try {
      const response = await api.get("/admin/profile", {
        headers: { Authorization: `Bearer ${token}` }
      });
      return response.data;
    } catch (error) {
      return rejectWithValue(error.response?.data?.message || "验证失败");
    }
  }
);

const adminSlice = createSlice({
  name: "admin",
  initialState: {
    token: localStorage.getItem("user-token"),
    isLoading: false,
    error: false,
    errorMessage: "",
    adminData: null
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(getAdminByToken.pending, (state) => {
        state.isLoading = true;
        state.error = false;
      })
      .addCase(getAdminByToken.fulfilled, (state, action) => {
        state.isLoading = false;
        state.adminData = action.payload;
      })
      .addCase(getAdminByToken.rejected, (state, action) => {
        state.isLoading = false;
        state.error = true;
        state.errorMessage = action.payload as string;
        localStorage.removeItem("user-token");
        state.token = null;
      });
  }
});

export default adminSlice.reducer;

内容的提问来源于stack exchange,提问作者Ehab reda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 20:42:32