登录跳转页面后React Navbar未更新的技术问题求助
核心问题分析
- token变量初始化时机错误:你在组件外部声明的
token只会在组件首次加载时读取一次localStorage,登录后localStorage更新但该变量不会重新获取,导致渲染逻辑失效。 - AvatarDropdown组件存在未定义变量:
setIsOpen和handleLogout未在组件内声明,会引发运行时错误。 - Redux状态未被正确利用:你尝试监听
user.accessToken,但如果redux中的用户状态未在登录后同步更新,监听不会触发重渲染。
具体修复步骤
1. 修正token的响应式获取
移除组件外部的token变量,改为以下两种方案之一:
方案一:组件内维护token状态并监听localStorage
import React,{forwardRef, useEffect, useState} from "react"; import { logoutUser } from "@/features/userSlice"; import { useDispatch, useSelector } from "react-redux"; import { getUserInfo } from "@/features/userSlice"; // 其他导入保持不变 const Header = forwardRef((props, ref) => { const navigate = useNavigate(); const dispatch = useDispatch(); // 初始化时读取localStorage,后续通过监听更新 const [token, setToken] = useState(localStorage.getItem("token")); const [isOpen, setIsOpen] = useState(false); // 监听localStorage变化,同步token状态 useEffect(() => { const syncToken = () => setToken(localStorage.getItem("token")); window.addEventListener("storage", syncToken); // 手动触发一次,避免同页面更新不触发storage事件 return () => window.removeEventListener("storage", syncToken); }, []); // 登出逻辑 const handleLogout = () => { dispatch(logoutUser()); localStorage.removeItem("token"); setToken(null); navigate("/register-login"); }; // 把AvatarDropdown移到Header内部,共享状态和方法 const AvatarDropdown = () => { return ( <DropdownMenu modal={true} onOpenChange={(open) => setIsOpen(open)}> <DropdownMenuTrigger className="outline-none"> <Avatar> <AvatarImage src="https://github.com/shadcn.png" /> <AvatarFallback>CN</AvatarFallback> </Avatar> </DropdownMenuTrigger> <DropdownMenuContent align="end" className="text-center"> <DropdownMenuItem onClick={handleLogout} className="px-5 py-1 " > <LogOut className="mr-3" /> Logout </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ); }; return ( <main className="w-full bg-orange-400 h-fit md:px-[100px] px-[40px] py-2 flex items-center justify-between box-border fixed top-0 z-10" ref={ref} > <div className=" md:flex hidden w-[70%] items-center gap-5"> <div className="w-full flex flex-1 items-center justify-end gap-5"> {/* 用组件内的token状态判断 */} {!token && ( <NavLink className={({ isActive }) => `p-2 font-semibold rounded-md bg-teal-400 text-white hover:bg-teal-500 active:bg-teal-700 ${isActive ? "border-[2px]" : ""}` } to="/register-login" > Register / Login </NavLink> )} <NavLink className={({ isActive }) => `p-2 font-semibold rounded-md bg-indigo-400 text-white hover:bg-indigo-500 active:bg-indigo-700 ${isActive ? "border-[2px]" : ""}` } to="/" > All Memories </NavLink> {token && ( <NavLink className={({ isActive }) => `border p-2 font-semibold rounded-md text-black hover:bg-orange-500 active:bg-orange-700 ${isActive ? "border-[2px]" : ""}` } to="/my-memories" > My Memories </NavLink> )} </div> <div>{token && <AvatarDropdown />}</div> </div> {/* 移动端菜单同理,替换所有token判断 */} <div className="md:hidden flex flex-row-reverse items-center space-x-5"> {token && ( <div className="ml-3"> <AvatarDropdown /> </div> )} <DropdownMenu modal={true} onOpenChange={(open) => setIsOpen(open)}> <DropdownMenuTrigger className="outline-none"> <i className="fa-solid fa-bars text-2xl p-2 text-black"></i> </DropdownMenuTrigger> <DropdownMenuContent align="end" className="text-center"> <DropdownMenuItem onClick={() => navigate("/register-login")} className="px-5 py-1 " disabled={token} > <Plus className="mr-3" /> Register or Login </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem onClick={() => navigate("/")} className="px-5 py-1 " > <Users className="mr-3" /> All Memories </DropdownMenuItem> <DropdownMenuSeparator /> <DropdownMenuItem onClick={() => navigate("/my-memories")} className="px-5 py-1" disabled={!token} > <User className="mr-3" /> My Memories </DropdownMenuItem> <DropdownMenuSeparator /> </DropdownMenuContent> </DropdownMenu> </div> </main> ); }); export default Header;
方案二:直接依赖Redux全局状态(更规范)
确保登录成功后,redux的user状态已更新accessToken,然后直接从redux获取:
const Header = forwardRef((props, ref) => { const navigate = useNavigate(); const dispatch = useDispatch(); const user = useSelector(getUserInfo); const token = user?.accessToken; // 直接从redux取token const [isOpen, setIsOpen] = useState(false); // 登出逻辑同上... });
2. 修复Layout组件的useEffect依赖问题
headerRef.current不能作为依赖,改用回调ref监听高度变化:
import { useCallback, useEffect, useState } from "react"; import { Suspense } from "react"; import { Outlet } from "react-router-dom"; import Header from "./Header"; import MemoriesHeader from "./MemoriesHeader"; const Layout = () => { const [headerHeight, setHeaderHeight] = useState(0); // 回调ref获取初始高度 const headerRef = useCallback((node) => { if (node) setHeaderHeight(node.offsetHeight); }, []); // 监听窗口resize更新高度 useEffect(() => { const updateHeight = () => { const header = document.querySelector('.bg-orange-400'); if (header) setHeaderHeight(header.offsetHeight); }; window.addEventListener('resize', updateHeight); return () => window.removeEventListener('resize', updateHeight); }, []); return ( <> <Header ref={headerRef} /> <Suspense fallback={<div>Loading...</div>}> <div className="max-w-[1484px] flex flex-col items-center m-auto"> <MemoriesHeader /> <Outlet context={[headerHeight]} /> </div> </Suspense> </> ); }; export default Layout;
3. 登录组件同步Redux状态
确保loginUser action已将accessToken存入redux的user状态,这样Header的useSelector才能获取到最新值:
// 登录组件的handleSubmit const handleSubmit = async(e) => { e.preventDefault(); try { const actionResult = await dispatch(loginUser(loginForm)).unwrap(); showToast("bg-green-700", "Success", "Login successfully."); localStorage.setItem("token", actionResult.data.accessToken); navigate("/my-memories"); } catch (error) { showToast("bg-red-700", "Error", error.errorMessage); } };
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

