我目前正在使用以下堆栈构建应用程序: Next.js Node.js、Express.js PostgreSQL。
我使用 JWT HTTPOnly cookie 构建身份验证。下面是我如何登录、注销和验证用户是否登录的代码。
在前端,我在 nextjs 中使用 RTK 查询来处理 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>
);
};
我按以下方式使用反应上下文来检查用户是否经过身份验证,如果是,我以类似的方式更新导航链接
"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
下面是我的登录组件
"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
我面临的问题是,每当我登录或注销时,反应上下文都不会更新状态,直到我刷新页面为止,如何确保状态已更新,更改已推送。我对如何修复它完全一片空白。请帮忙!
感谢您对此进行调查。
我尝试寻找不同的方法来解决这个问题,例如 useEffect、useState,我尝试将 React 上下文更改为 redux 切片,但我找不到解决此问题的方法。
感谢大家对此进行调查。我终于找到了解决方案。这是代码。我只需要使用钩子。
"use client"
import { ContextI } from '@/interfaces';
import { useVerifyQuery } from '@/lib/features/authApi';
import { createContext, PropsWithChildren, useEffect, useState } from 'react';
const initialContext = {
user: null,
isAuthenticated: false,
error: null,
isLoading: false,
} as ContextI;
export const SessionContext = createContext<ContextI>(initialContext);
export const SessionProvider = ({ children }: PropsWithChildren) => {
const { data, isLoading } = useVerifyQuery();
const user = data?.data ?? null;
const error = data?.error ?? null;
const [context, setContext] = useState<ContextI>({
user,
isAuthenticated: !!data?.data,
error,
isLoading,
updateContext: (newContext: Partial<ContextI>) => {
setContext((prevContext) => ({
...prevContext,
...newContext,
}));
}
});
useEffect(() => {
setContext((prevContext) => ({
...prevContext,
user,
isAuthenticated: !!user,
error,
isLoading
}));
}, [data, isLoading]);
return (
<SessionContext.Provider value={{ ...context }}>
{children}
</SessionContext.Provider>
);
};