如何在登录/登出后无需刷新页面更新React Context状态?
问题:登录/登出后React Context状态无法即时更新
技术栈
- Next.js
- Node.js + Express.js
- PostgreSQL
- JWT HTTPOnly Cookie 认证
- RTK Query 处理API请求
当前实现代码
SessionProvider组件
"use client" import { UserI } from '@/interfaces'; import { useVerifyQuery } from '@/lib/features/authApi'; import { createContext, PropsWithChildren } from 'react'; interface ContextI { user: UserI | null; isAuthenticated: boolean; error: string | null; isLoading: boolean; } export const SessionContext = createContext<ContextI>({ user: null, isAuthenticated: false, error: null, isLoading: false }); export const SessionProvider = ({ children }: PropsWithChildren) => { const { data, isLoading } = useVerifyQuery(); const user = data?.data; const error = data?.error; if (error) console.log(error); return ( <SessionContext.Provider value={{ user: user ?? null, isAuthenticated: user ? true : false, error: error ?? null, isLoading }}> {children} </SessionContext.Provider> ); };
导航链接组件(NavLinks)
"use client" import { SessionContext } from '@/app/SessionProvider'; import { ButtonGroup, Flex, HStack, Spinner } from '@chakra-ui/react'; import { usePathname } from 'next/navigation'; import { useContext } from 'react'; import LinkBtn from './LinkBtn'; import Logout from './Logout'; const NavLinks = () => { const { user, isAuthenticated, isLoading } = useContext(SessionContext); const currPath = usePathname(); if (isLoading) return <Spinner /> return ( <Flex className='gap-3 justify-center items-center'> {isAuthenticated ? <> <HStack> <LinkBtn href={currPath === '/' ? '/dashboard' : '/'}> {currPath === '/' ? 'Dashboard' : 'Home'} </LinkBtn> <Logout name={user?.name} /> </HStack> </> : <ButtonGroup> <LinkBtn href='/user/signup'>Sign up</LinkBtn> <LinkBtn href='/user/login'>Login</LinkBtn> </ButtonGroup> } </Flex> ) } export default NavLinks
登录组件(Login)
"use client" import { handleErrors } from '@/components/error/handleErrors' import { toast } from "@/components/error/Toast" import Auth from '@/components/ui/Auth' import Btn from '@/components/ui/Btn' import ErrorMsg from '@/components/ui/ErrorMsg' import LinkBtn from '@/components/ui/LinkBtn' import Logo from '@/components/ui/Logo' import { unexpectedError } from '@/constants' import { AuthI } from '@/interfaces' import { useLoginMutation } from '@/lib/features/authApi' import { validateLogin } from '@/validation' import { ButtonGroup, Input, Spinner } from '@chakra-ui/react' import { zodResolver } from '@hookform/resolvers/zod' import { useRouter } from 'next/navigation' import { useForm } from "react-hook-form" const Login = () => { const { register, handleSubmit, formState: { errors } } = useForm<AuthI>({ resolver: zodResolver(validateLogin) }); const navigation = useRouter(); const [login, { isLoading, error }] = useLoginMutation(); if (error) handleErrors(error, unexpectedError.type); const onSubmit = async (data: AuthI) => { try { await login(data); navigation.push("/"); } catch (error) { toast.error(unexpectedError.message, { toastId: unexpectedError.type }); console.log(error); } }; return ( <Auth> <form className='flex flex-col gap-2 p-10' onSubmit={handleSubmit(onSubmit)}> <Logo /> {errors && <ErrorMsg>{errors.email?.message}</ErrorMsg>} <Input type='email' isRequired placeholder='Enter your email...' {...register("email")} /> {errors && <ErrorMsg>{errors.password?.message}</ErrorMsg>} <Input type='password' isRequired placeholder='Enter your password...' {...register("password")} /> <ButtonGroup my={2}> <Btn type='submit' isDisabled={isLoading}> Login {isLoading && <Spinner ml={1} size='sm' />} </Btn> <LinkBtn href='/'> Cancel </LinkBtn> </ButtonGroup> </form> </Auth> ) } export default Login
核心问题
执行登录或登出操作后,React Context中的认证状态不会即时更新,必须刷新页面才能同步状态。尝试过useEffect、useState以及将Context替换为Redux Slice,均未解决问题。
解决方案
1. 给SessionProvider添加会话刷新能力
当前useVerifyQuery仅在组件挂载时执行一次,登录/登出后Cookie变化但查询不会自动重新运行。需要获取RTK Query的refetch方法并暴露给上下文:
"use client" import { UserI } from '@/interfaces'; import { useVerifyQuery } from '@/lib/features/authApi'; import { createContext, PropsWithChildren, useCallback } from 'react'; interface ContextI { user: UserI | null; isAuthenticated: boolean; error: string | null; isLoading: boolean; refreshSession: () => void; // 添加刷新方法 } export const SessionContext = createContext<ContextI>({ user: null, isAuthenticated: false, error: null, isLoading: false, refreshSession: () => {}, }); export const SessionProvider = ({ children }: PropsWithChildren) => { const { data, isLoading, refetch } = useVerifyQuery(); // 获取refetch方法 const user = data?.data; const error = data?.error; // 定义刷新会话的方法 const refreshSession = useCallback(() => { refetch(); }, [refetch]); if (error) console.log(error); return ( <SessionContext.Provider value={{ user: user ?? null, isAuthenticated: user ? true : false, error: error ?? null, isLoading, refreshSession // 传入上下文 }} > {children} </SessionContext.Provider> ); };
2. 在登录/登出完成后触发会话刷新
修改Login组件,登录成功后刷新状态
const Login = () => { // ... 原有代码 const { refreshSession } = useContext(SessionContext); // 引入刷新方法 const onSubmit = async (data: AuthI) => { try { await login(data); await refreshSession(); // 登录后立即刷新会话状态 navigation.push("/"); } catch (error) { toast.error(unexpectedError.message, { toastId: unexpectedError.type }); console.log(error); } }; // ... 原有代码 }
修改Logout组件,登出后刷新状态
假设Logout组件逻辑如下,添加刷新调用:
"use client" import { useLogoutMutation } from '@/lib/features/authApi'; import { useContext } from 'react'; import { SessionContext } from '@/app/SessionProvider'; import { useRouter } from 'next/navigation'; const Logout = ({ name }: { name?: string }) => { const [logout] = useLogoutMutation(); const { refreshSession } = useContext(SessionContext); const router = useRouter(); const handleLogout = async () => { try { await logout(); await refreshSession(); // 登出后立即刷新会话状态 router.push("/"); } catch (error) { console.error(error); } }; return ( <button onClick={handleLogout}>Logout {name}</button> ); }; export default Logout;
3. 可选:优化RTK Query缓存配置
在authApi的查询定义中,添加自动重查规则,确保Cookie变化时状态同步:
// 在authApi的定义文件中 export const authApi = createApi({ reducerPath: 'authApi', baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: (builder) => ({ verify: builder.query({ query: () => '/auth/verify', refetchOnMountOrArgChange: true, // 组件挂载时自动重查 refetchOnReconnect: true, // 网络恢复时自动重查 }), // ...其他端点 }), });
问题根源说明
- 之前用
useState但未和RTK Query的缓存状态同步,导致Context无法感知后端认证状态变化。 useEffect无法直接监听Cookie变化,只能通过主动触发查询刷新来同步状态。- 换成Redux Slice后未在登录/登出后重新拉取用户信息,Slice状态与后端认证状态脱节。
内容的提问来源于stack exchange,提问作者Scorpion King
相关产品推荐
相关产品推荐

