在Next.js 15中结合Mantine Form与Server Actions实现客户端和服务端双重验证的问题
在Next.js 15中结合Mantine Form与Server Actions实现客户端和服务端双重验证的问题
我完全理解你的困扰——想用Mantine Form便捷的客户端验证能力,同时搭配Next.js Server Actions完成服务端校验,但之前的写法始终无法触发服务端动作对吧?其实问题核心在于Mantine Form的onSubmit默认会阻止原生表单提交行为,你之前手动调用formRef.current?.submit()的方式因为事件已经被拦截,所以无法生效。下面给你两种可行的解决方案,帮你实现两端验证的协同工作:
方案一:贴合原生表单的Server Action用法
这种方式保留Next.js原生表单提交的逻辑,先通过Mantine完成客户端验证,验证通过后再触发Server Action,同时同步服务端返回的错误到表单中。
第一步:编写Server Action(比如app/actions.ts)
我们可以用Zod定义统一的验证Schema,让客户端和服务端复用同一套规则,避免逻辑不一致:
'use server'; import { z } from 'zod'; // 共享的验证Schema const registrationSchema = z.object({ name: z.string().min(2, 'Name must have at least 2 letters'), email: z.string().email('Invalid email'), age: z.number().min(18, 'You must be at least 18 to register'), }); export async function registerUser(formData: FormData) { // 从FormData提取数据 const data = { name: formData.get('name') as string, email: formData.get('email') as string, age: Number(formData.get('age')), }; // 服务端验证 const validationResult = registrationSchema.safeParse(data); if (!validationResult.success) { // 返回Mantine Form可识别的错误格式 return { errors: validationResult.error.flatten().fieldErrors, }; } // 这里写实际业务逻辑,比如写入数据库 console.log('注册用户信息:', validationResult.data); return { success: true }; }
第二步:编写客户端表单组件
利用useFormState获取服务端提交结果,同时在原生表单的onSubmit中先执行Mantine的客户端验证:
'use client'; import { useForm } from '@mantine/form'; import { NumberInput, TextInput, Button, Alert } from '@mantine/core'; import { useFormState } from 'react-dom'; import { registerUser } from './actions'; function RegistrationForm() { // 获取服务端提交的结果 const [submitResult, formAction] = useFormState(registerUser, undefined); const form = useForm({ initialValues: { name: '', email: '', age: 0 }, // 客户端验证规则(和服务端Schema保持一致) validate: { name: (value) => value.length < 2 ? 'Name must have at least 2 letters' : null, email: (value) => /^\S+@\S+$/.test(value) ? null : 'Invalid email', age: (value) => value < 18 ? 'You must be at least 18 to register' : null, }, // 同步服务端返回的错误到表单 initialErrors: submitResult?.errors || {}, }); return ( <form action={formAction} onSubmit={(e) => { // 先执行客户端验证 const isClientValid = form.validate(); if (!isClientValid) { // 验证不通过,阻止原生提交 e.preventDefault(); } // 验证通过则自动触发Server Action }} > {/* 显示服务端全局反馈 */} {submitResult?.success ? ( <Alert color="green" mt="sm">注册成功!</Alert> ) : submitResult?.errors && Object.keys(submitResult.errors).length > 0 ? ( <Alert color="red" mt="sm">提交失败,请检查表单内容</Alert> ) : null} <TextInput label="Name" placeholder="Name" key={form.key('name')} {...form.getInputProps('name')} /> <TextInput mt="sm" label="Email" placeholder="Email" key={form.key('email')} {...form.getInputProps('email')} /> <NumberInput mt="sm" label="Age" placeholder="Age" min={0} max={99} key={form.key('age')} {...form.getInputProps('age')} /> <Button type="submit" mt="sm"> Submit </Button> </form> ); } export default RegistrationForm;
方案二:直接在Mantine回调中调用Server Action
这种方式更灵活,不需要依赖原生表单的action属性,直接在Mantine的提交回调里调用Server Action函数,手动处理结果:
'use client'; import { useState } from 'react'; import { useForm } from '@mantine/form'; import { NumberInput, TextInput, Button, Alert } from '@mantine/core'; import { registerUser } from './actions'; function RegistrationForm() { const form = useForm({ initialValues: { name: '', email: '', age: 0 }, validate: { name: (value) => value.length < 2 ? 'Name must have at least 2 letters' : null, email: (value) => /^\S+@\S+$/.test(value) ? null : 'Invalid email', age: (value) => value < 18 ? 'You must be at least 18 to register' : null, }, }); // 存储服务端提交状态 const [submitState, setSubmitState] = useState<{ success?: boolean; errors?: Record<string, string[]> }>(undefined); const handleSubmit = async (values: typeof form.values) => { // 手动触发客户端验证(Mantine的onSubmit其实会自动做,这里加了更保险) const isClientValid = form.validate(); if (!isClientValid) return; try { // 构造FormData传给Server Action const formData = new FormData(); formData.append('name', values.name); formData.append('email', values.email); formData.append('age', values.age.toString()); const result = await registerUser(formData); if (result.errors) { // 将服务端错误同步到表单 form.setErrors(result.errors); setSubmitState({ errors: result.errors }); } else { setSubmitState({ success: true }); // 验证成功后重置表单 form.reset(); } } catch (error) { setSubmitState({ errors: { _root: ['服务器出错,请稍后重试'] } }); } }; return ( <form onSubmit={form.onSubmit(handleSubmit)}> {/* 显示全局反馈 */} {submitState?.success ? ( <Alert color="green" mt="sm">注册成功!</Alert> ) : submitState?.errors?._root ? ( <Alert color="red" mt="sm">{submitState.errors._root[0]}</Alert> ) : null} <TextInput label="Name" placeholder="Name" key={form.key('name')} {...form.getInputProps('name')} /> <TextInput mt="sm" label="Email" placeholder="Email" key={form.key('email')} {...form.getInputProps('email')} /> <NumberInput mt="sm" label="Age" placeholder="Age" min={0} max={99} key={form.key('age')} {...form.getInputProps('age')} /> <Button type="submit" mt="sm"> Submit </Button> </form> ); } export default RegistrationForm;
这两种方案都能完美实现客户端+服务端的双重验证,你可以根据自己的业务场景选择:方案一更贴合Next.js的原生表单提交逻辑,方案二更灵活,适合需要自定义提交流程的场景。
备注:内容来源于stack exchange,提问作者McFlurriez
相关产品推荐
相关产品推荐

