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

React Router v6:不使用localStorage实现登出后保留上一路由

问题:React项目登出后无法保留最后访问路由(禁用localStorage)

描述

正在开发基于React Router v6和Apollo Client的React项目,需求是用户登出后保留其最后访问的路由,且不能使用localStorage。当前用户登出后,上一路由会被重置为默认值"/",而非保留最后访问的路由。

核心问题

登出过程中previousRouteRef会被重置为默认值"/",需要让它持久保留最后访问的路由。

预期行为

previousRouteRef.current能存储用户最后访问的路由,且在登出后保持不变,以便用户重新登录时可自动重定向到登出前的页面。

观察结果

登出时RoutesWrapper组件会重渲染三次,推测重复渲染导致previousRouteRef被重置为默认路由。

渲染计数验证

添加了渲染计数器跟踪RoutesWrapper的渲染次数,并在每次渲染时打印previousRouteRef的值,日志确认重渲染后previousRouteRef.current被重置为"/"。

涉及文件

RoutesWrapper.js

该组件负责路由管理与登录状态检查,包含用于存储最后访问路由的previousRouteRef:

import { useEffect, useRef, useState } from "react";
import LineWobbleLoader from "@/components/customs/helpers/page-loader";
import useAuthRedirect from "@/hooks/use-auth-redirect";
import MainLayout from "@/layout/main-layout";
import PageLayout from "@/layout/page-layout";
import ForgotPassword from "@/pages/auth-form/login/forgot-password-page";
import { Login } from "@/pages/auth-form/login/login";
import ResetPassword from "@/pages/auth-form/login/reset-password";
import { SignUp } from "@/pages/auth-form/signup/signup";
import SignUpVerify from "@/pages/auth-form/signup/signup-verify";
import AddContact from "@/pages/contacts/add-contact";
import Contacts from "@/pages/contacts/contacts";
import Dashboard from "@/pages/dashboard/dashboard";
import MessageTemplate from "@/pages/messages/message-templates";
import SetupPage from "@/pages/setup/setup-page";
import SuccessPage from "@/pages/success/success-page";
import CreateTemplate from "@/pages/templates/add-template";
import { Navigate, Route, Routes } from "react-router-dom";

function RoutesWrapper() {
  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
  const previousRouteRef = useRef<string>("/");
  const renderCount = useRef(0);
  
  const { meData, whatsappData, meLoading, whatsappLoading, refetchMe } =
    useAuthRedirect({ isLoggedIn });

  renderCount.current += 1;
  console.log(`RoutesWrapper render count: ${renderCount.current}`);

  useEffect(() => {
    refetchMe();
  }, [isLoggedIn, refetchMe]);

  if (meLoading || whatsappLoading) {
    return (
      <div className="h-screen flex justify-center items-center">
        <LineWobbleLoader />
      </div>
    );
  }

  return (
    <Routes>
      {!meData ? (
        <>
          <Route path="/" element={<Login setIsLoggedIn={setIsLoggedIn} previousRouteRef={previousRouteRef} />} />
          <Route path="/signup" element={<SignUp />} />
          <Route path="/forgot-password" element={<ForgotPassword />} />
          <Route path="/reset-password" element={<ResetPassword />} />
          <Route path="/success-page" element={<SuccessPage />} />
        </>
      ) : (
        <>
          <Route path="/" element={whatsappData?.getWhatsApp?.business ? <Navigate to="/contacts" replace /> : <Navigate to="/setup" replace />} />
          <Route path="/setup" element={<SetupPage setIsLoggedIn={setIsLoggedIn} previousRouteRef={previousRouteRef} />} />
          <Route element={<PageLayout />}>
            {whatsappData?.getWhatsApp?.business ? (
              <Route element={<MainLayout previousRouteRef={previousRouteRef} meData={meData.me} setIsLoggedIn={setIsLoggedIn} />}>
                <Route path="/dashboard" element={<Dashboard />} />
                <Route path="/contacts" element={<Contacts />} />
                <Route path="/addcontact" element={<AddContact />} />
                <Route path="/templates" element={<CreateTemplate />} />
                <Route path="/newtemplate" element={<MessageTemplate />} />
                <Route path="*" element={<Navigate to="/" replace />} />
              </Route>
            ) : (
              <Route path="*" element={<Navigate to="/setup" replace />} />
            )}
          </Route>
        </>
      )}
    </Routes>
  );
}

export default RoutesWrapper;

useLogout.js

处理登出流程的Hook,本应在登出前将previousRouteRef设置为当前路径名:

import { useMutation } from "@apollo/client";
import { LOGOUT } from "@/services/gpl/mutations";

const useLogout = ({ setIsLoggedIn, previousRouteRef }) => {
  const [logout] = useMutation(LOGOUT);

  const handleLogout = () => {
    previousRouteRef.current = window.location.pathname; // Persisting the current route
    logout()
      .then(() => {
        setIsLoggedIn(false);
        window.location.href = "/";
      })
      .catch(error => {
        console.error("Logout error:", error);
      });
  };

  return { handleLogout };
};

export default useLogout;

useLogin.tsx

处理登录流程的Hook,登录后会根据previousRouteRef重定向:

import { useLazyQuery, useApolloClient } from "@apollo/client";
import { LOGIN, ME } from "@/services/gpl/queries";
import { useToast } from "@/components/ui/use-toast";
import { useNavigate } from "react-router-dom";

interface LoginInput {
  email: string;
  password: string;
}

type Props = {
  setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;
  previousRouteRef: React.MutableRefObject<string>;
};

const useLogin = ({ setIsLoggedIn, previousRouteRef }: Props) => {
  const { toast } = useToast();
  const navigate = useNavigate();
  const client = useApolloClient();
  const [login, { loading, error, data }] = useLazyQuery(LOGIN);

  const handleLogin = (loginInput: LoginInput) => {
    login({ variables: { login: loginInput } })
      .then((response) => {
        if (response.data?.login) {
          setIsLoggedIn(true);
          const cachedMeData = client.readQuery({ query: ME });
          if (cachedMeData && cachedMeData.me) {
            if (cachedMeData.me.email === response.data.login.email) {
              navigate(previousRouteRef.current || "/contacts");
              return;
            }
          }
          navigate("/contacts");
        } else {
          toast({
            title: "Failed",
            description: error?.message,
            className: "text-red-600 border-red-600",
          });
        }
      })
      .catch((error) => {
        console.log(error);
        toast({
          description: "Invalid email or password",
          className: "text-red-600 border-red-600",
        });
      });
  };

  return {
    loading,
    error,
    data,
    handleLogin,
  };
};

export default useLogin;

已尝试方案

  • 使用useRef定义previousRouteRef存储登出前的最后路由,但登出后值被重置为"/";
  • 在handleLogout中手动设置previousRouteRef.current = window.location.pathname,但仍会被重置;
  • 调试渲染次数,确认登出时组件多次重渲染可能影响状态。

解决方案

问题根源

调用window.location.href = "/"会导致页面全量刷新,此时整个React应用重新初始化,组件内的useRef会被重置为初始值"/",这是问题的核心。

修复方案

改用URL查询参数传递登出前的路由,避免页面刷新,具体步骤:

  1. 修改useLogout.js,替换页面刷新为React Router跳转并携带路由参数:
import { useMutation } from "@apollo/client";
import { LOGOUT } from "@/services/gpl/mutations";
import { useNavigate } from "react-router-dom";

const useLogout = ({ setIsLoggedIn, previousRouteRef }) => {
  const [logout] = useMutation(LOGOUT);
  const navigate = useNavigate();

  const handleLogout = () => {
    const currentPath = window.location.pathname;
    // 排除登录相关页面,不需要保存这些路由
    if (!["/", "/signup", "/forgot-password", "/reset-password"].includes(currentPath)) {
      previousRouteRef.current = currentPath;
    }
    
    logout()
      .then(() => {
        setIsLoggedIn(false);
        // 携带路由参数跳转,不刷新页面
        navigate(`/?redirect=${encodeURIComponent(previousRouteRef.current)}`);
      })
      .catch(error => {
        console.error("Logout error:", error);
      });
  };

  return { handleLogout };
};

export default useLogout;
  1. 修改RoutesWrapper.js,读取URL参数更新previousRouteRef:
import { useEffect, useRef, useState } from "react";
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
// 其他导入不变

function RoutesWrapper() {
  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
  const previousRouteRef = useRef<string>("/");
  const renderCount = useRef(0);
  const location = useLocation();
  
  const { meData, whatsappData, meLoading, whatsappLoading, refetchMe } =
    useAuthRedirect({ isLoggedIn });

  // 读取URL中的redirect参数,更新ref
  useEffect(() => {
    const searchParams = new URLSearchParams(location.search);
    const redirectPath = searchParams.get("redirect");
    if (redirectPath) {
      previousRouteRef.current = decodeURIComponent(redirectPath);
    }
  }, [location.search]);

  renderCount.current += 1;
  console.log(`RoutesWrapper render count: ${renderCount.current}`);

  useEffect(() => {
    refetchMe();
  }, [isLoggedIn, refetchMe]);

  // 已登录时自动跳转到保存的路由,同时清除URL参数
  return (
    <Routes>
      {!meData ? (
        <>
          <Route path="/" element={<Login setIsLoggedIn={setIsLoggedIn} previousRouteRef={previousRouteRef} />} />
          <Route path="/signup" element={<SignUp />} />
          <Route path="/forgot-password" element={<ForgotPassword />} />
          <Route path="/reset-password" element={<ResetPassword />} />
          <Route path="/success-page" element={<SuccessPage />} />
        </>
      ) : (
        <>
          <Route path="/" element={
            whatsappData?.getWhatsApp?.business 
              ? <Navigate to={previousRouteRef.current || "/contacts"} replace /> 
              : <Navigate to="/setup" replace />
          } />
          <Route path="/setup" element={<SetupPage setIsLoggedIn={setIsLoggedIn} previousRouteRef={previousRouteRef} />} />
          <Route element={<PageLayout />}>
            {whatsappData?.getWhatsApp?.business ? (
              <Route element={<MainLayout previousRouteRef={previousRouteRef} meData={meData.me} setIsLoggedIn={setIsLoggedIn} />}>
                <Route path="/dashboard" element={<Dashboard />} />
                <Route path="/contacts" element={<Contacts />} />
                <Route path="/addcontact" element={<AddContact />} />
                <Route path="/templates" element={<CreateTemplate />} />
                <Route path="/newtemplate" element={<MessageTemplate />} />
                <Route path="*" element={<Navigate to="/" replace />} />
              </Route>
            ) : (
              <Route path="*" element={<Navigate to="/setup" replace />} />
            )}
          </Route>
        </>
      )}
    </Routes>
  );
}

export default RoutesWrapper;

效果说明

修改后登出时不会刷新页面,通过React Router跳转并携带路由参数,保证previousRouteRef的值不会被重置,同时满足不使用localStorage的要求,用户登录后会自动重定向到登出前的页面。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:38:15