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

React Router跳转后URL更新但页面不渲染问题求助

React Router登录跳转后URL更新但页面不渲染问题排查与解决

问题描述

React新手在实现登录跳转功能时遇到异常:用户登录成功后,/account/login路由的action返回redirect('/'),浏览器URL从localhost:3000/account/login变为localhost:3000,但首页未渲染,登录表单仍保留。尝试跳转至其他URL(如/profile)也出现相同情况:URL更新但页面组件不切换。

核心问题分析

  1. Router实例重复创建:RenderRoot组件中每次useAuth()返回的上下文变化时,都会重新调用router(auth)创建新的BrowserRouter实例,导致React Router无法维持路由状态一致性,跳转后页面不更新。
  2. Axios拦截器错误使用redirect:redirect是React Router专属的action/loader方法,不能在Axios拦截器中直接返回,此操作无效且会导致拦截器逻辑异常。
  3. 组件渲染阶段直接调用setState:LoginPage中在组件顶层直接根据useActionData()返回值修改状态,会触发无限渲染循环,干扰页面正常更新。

解决方案及代码修正

1. 重构Router创建逻辑,确保单例实例

将Router的创建移至AuthProvider内部,避免因上下文变化重复创建Router实例。

修改后的index.js:

import { AuthProvider } from "./bridge/AuthProvider";
import ReactDOM from 'react-dom/client';
import { StrictMode } from "react";

const apiClient = createApiClient();

ReactDOM.createRoot(document.getElementById("root")).render(
  <StrictMode>
    <AuthProvider apiClient={apiClient} />
  </StrictMode>
);

2. 修正Axios拦截器跳转逻辑

使用React Router的useNavigate钩子替代redirect处理未授权跳转。

修改后的AuthProvider.js:

import React, { useContext, useEffect, useNavigate } from "react";
import { redirect, createBrowserRouter, RouterProvider } from "react-router-dom";
import Root from "./Root";
import ErrorPage from "./ErrorPage";
import Home from "./Home";
import { LoginPage } from "./account";

const AuthContext = React.createContext({});

export function AuthProvider({ apiClient }) {
  const [identity, setIdentity] = React.useState({});
  const navigate = useNavigate();

  useEffect(() => {
    const requestInterceptor = apiClient.interceptors.request.use((config) => {
      if (config.data instanceof FormData) {
        let object = {};
        config.data.forEach((value, key) => (object[key] = value));
        config.data = JSON.stringify(object);
      }
      return config;
    });

    const responseInterceptor = apiClient.interceptors.response.use(
      (res) => res,
      (err) => {
        if (err.response?.status === 401 || err.response?.status === 403) {
          setIdentity({});
          navigate("/account/login"); // 使用navigate触发跳转
        }
        console.error("rejecting response ", err);
        return Promise.reject(err);
      }
    );

    return () => {
      apiClient.interceptors.request.eject(requestInterceptor);
      apiClient.interceptors.response.eject(responseInterceptor);
    };
  }, [apiClient, navigate]);

  async function login({ request, params }) {
    try {
      setIdentity({});
      const formData = await request.formData();
      const body = Object.fromEntries(formData);
      const res = await apiClient.post("/api/auth/login", body);
      if (res.data && typeof res.data === "object") {
        const newIdentity = {};
        if ("univID" in res.data) newIdentity.univID = res.data.univID;
        if ("email" in res.data) newIdentity.email = res.data.email;
        if ("id" in res.data) newIdentity.id = res.data.id;

        if (newIdentity.univID || newIdentity.email || newIdentity.id)
          setIdentity(newIdentity);
      }
      return redirect("/");
    } catch (error) {
      return error.response;
    }
  }

  const context = { login };

  // 单例Router实例
  const router = createBrowserRouter([
    {
      path: "/",
      element: <Root />,
      errorElement: <ErrorPage />,
      id: "root",
      children: [
        {
          errorElement: <ErrorPage />,
          children: [
            { index: true, element: <Home /> },
            {
              path: "account/login",
              action: login,
              element: <LoginPage />,
            },
          ],
        },
      ],
    },
  ]);

  return (
    <AuthContext.Provider value={context}>
      <RouterProvider router={router} />
    </AuthContext.Provider>
  );
}

export default function useAuth() {
  return useContext(AuthContext);
}

3. 避免渲染阶段修改状态

使用useEffect监听useActionData返回值,在副作用中更新表单验证状态。

修改后的account.jsx:

import { useState, useEffect } from "react";
import { useActionData } from "react-router-dom";

export function LoginPage() {
  const [valids, setValids] = useState({
    univID: true,
    password: true,
  });

  const err = useActionData();
  
  useEffect(() => {
    if (err?.status === 401 && err.data?.errors) {
      setValids({
        univID: !("univID" in err.data.errors),
        password: !("password" in err.data.errors),
      });
    }
  }, [err]);

  // 其余表单渲染与提交逻辑
}

关键修复总结

  • Router单例化:确保整个应用只有一个Router实例,维持路由状态一致性。
  • 拦截器跳转修正:用useNavigate替代redirect处理未授权场景,符合React Router API规范。
  • 状态更新时机调整:在useEffect中处理action返回的错误信息,避免渲染阶段触发状态更新导致的循环渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 06:47:50