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

在NextJS中结合Zustand处理Laravel登录验证错误

解决方案:在NextJS登录表单中展示Laravel返回的字段级验证错误

1. 扩展User Store存储字段级错误

首先修改useUserService.js,在状态中新增fieldErrors来保存后端返回的字段错误:

const initialState = {
    users: undefined,
    user: undefined,
    currentUser: undefined,
    fieldErrors: {} // 新增:专门存储字段级验证错误
}

const userStore = create(() => initialState)

2. 修改Login方法解析并存储错误

在login函数的异常捕获逻辑中,解析Laravel返回的errors字段,存入store的同时保留全局错误提示:

login: async (email, password) => {
    alertService.clear()
    // 清除上一次的字段错误
    userStore.setState({ fieldErrors: {} })

    try {
        const currentUser = await fetch.post('/api/authentication/token', {email, password})
        userStore.setState({ ...initialState, currentUser })

        const returnUrl = searchParams.get('returnUrl') || '/dashboard'
        router.push(returnUrl)
    } catch (error) {
        // 解析后端返回的字段错误
        const fieldErrors = error.response?.data?.errors || {}
        userStore.setState({ fieldErrors })
        
        // 显示全局错误消息
        const errorMessage = error.response?.data?.message || '登录失败'
        alertService.error(errorMessage)
    }
},

3. 在Login组件中展示对应字段错误

修改Login组件,从useUserService获取fieldErrors,并在对应输入框下方渲染错误文本,同时给错误状态的输入框添加红色边框提示:

export default Login

function Login() {
    const userService = useUserService()
    const { fieldErrors } = userService

    const { register, handleSubmit, formState } = useForm()

    async function onSubmit({ email, password }) {
        await userService.login(email, password)
    }

    return (
        <>
            <form onSubmit={handleSubmit(onSubmit)} className="mt-10 grid grid-cols-1 gap-y-8">
                <div>
                    <label
                        htmlFor="email"
                        className="mb-3 block text-sm font-medium text-gray-700"
                    >
                        Email address
                    </label>
                    <input
                        type="email"
                        { ...register("email") }
                        className={`block w-full appearance-none rounded-md border ${fieldErrors.email ? 'border-red-500' : 'border-gray-200'} bg-gray-50 px-3 py-2 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:bg-white focus:outline-none focus:ring-blue-500 sm:text-sm`}
                    />
                    {/* 渲染email字段错误 */}
                    {fieldErrors.email && (
                        <div className="mt-1 text-sm text-red-600">
                            {fieldErrors.email.join(', ')}
                        </div>
                    )}
                </div>
                <div>
                    <label
                        htmlFor="password"
                        className="mb-3 block text-sm font-medium text-gray-700"
                    >
                        Password
                    </label>
                    <input
                        { ...register("password") }
                        type="password"
                        className={`block w-full appearance-none rounded-md border ${fieldErrors.password ? 'border-red-500' : 'border-gray-200'} bg-gray-50 px-3 py-2 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:bg-white focus:outline-none focus:ring-blue-500 sm:text-sm`}
                    />
                    {/* 渲染password字段错误 */}
                    {fieldErrors.password && (
                        <div className="mt-1 text-sm text-red-600">
                            {fieldErrors.password.join(', ')}
                        </div>
                    )}
                </div>
                <div>
                    <button
                        disabled={formState.isSubmitting}
                        className={"w-full group inline-flex items-center justify-center rounded-md py-2 px-4 text-sm font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 bg-blue-600 text-white hover:text-slate-100 hover:bg-blue-500 active:bg-blue-800 active:text-blue-100 focus-visible:outline-blue-600 " + (formState.isSubmitting ? ButtonSpinner['btn-loading'] : "")}
                    >
                        <span>Sign in <span aria-hidden="true">&rarr;</span></span>
                    </button>
                </div>
            </form>
        </>
    )
}

说明

  • Laravel返回的错误是数组格式(比如email: ["The email field is required."]),所以用join(', ')把多个错误拼接成字符串
  • 每次提交前清除字段错误,避免旧错误干扰新的提交
  • 通过动态添加border-red-500类,让用户直观看到哪个输入框有问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:49:58