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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 10:29:32