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

MUI5 Stepper结合react-hook-form7实现分步表单的相关问题咨询

MUI 5 Stepper + react-hook-form 7 多步骤表单问题解决方案

分层defaultValues方案合理性

你按步骤分层定义defaultValues的方案完全合理,天然适配react-hook-form的嵌套字段特性,无需调整结构。

现有异常修复方案

1. 切换步骤表单值重置问题

  • 根因:未全局共用同一个useForm实例,步骤切换时子组件内的form实例被销毁重建,导致数据丢失。
  • 修复方法:将useForm实例定义在多步骤表单最外层父组件,通过FormProvider透传给所有子步骤组件,全程共用同一个form状态容器。

2. 表单校验失效问题

  • 根因:未做步骤级局部校验,全量校验会触发未渲染的后续步骤字段报错,或未关联到嵌套字段的校验规则。
  • 修复方法:切换下一步前,调用react-hook-form的trigger方法仅校验当前步骤对应的嵌套字段前缀,校验通过再跳转步骤。

需求实现方案

第一步数据单独提前提交

第一步校验通过后,直接调用getValues("step1")获取第一步所有字段数据,调用对应提交接口即可,无需等待整个表单完成。

最后一步全量数据汇总

最终提交时调用handleSubmit回调,拿到的参数就是完整的包含step1、step2的全量表单数据,直接使用即可。

核心代码示例

外层父组件代码

import { useState } from "react";
import { useForm, FormProvider } from "react-hook-form";
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import Button from '@mui/material/Button';
import Step1Form from "./Step1Form";
import Step2Form from "./Step2Form";

// 你定义的分层默认值
const defaultValues = {
  step1: { firstname: "", lastname: "" },
  step2: { address: "", city: "" }
}

const steps = ['注册信息填写', '地址信息填写'];

export default function MultiStepForm() {
  const [activeStep, setActiveStep] = useState(0);
  const methods = useForm({ defaultValues });
  const { trigger, getValues } = methods;

  // 下一步逻辑
  const handleNext = async () => {
    // 仅校验当前步骤的嵌套字段
    const isStepValid = await trigger(`step${activeStep + 1}`);
    if (!isStepValid) return;

    // 第一步校验通过后单独提交
    if (activeStep === 0) {
      const step1Data = getValues("step1");
      // 在此处调用第一步提交接口:await submitStep1(step1Data)
    }

    setActiveStep(prev => prev + 1);
  };

  // 上一步逻辑
  const handleBack = () => {
    setActiveStep(prev => prev - 1);
  };

  // 最终全量提交
  const handleFinalSubmit = methods.handleSubmit((allData) => {
    // allData为包含step1、step2的完整表单数据
    console.log("全量表单数据:", allData);
    // 在此处调用最终提交接口
  });

  return (
    <FormProvider {...methods}>
      <Stepper activeStep={activeStep}>
        {steps.map((label) => (
          <Step key={label}>
            <StepLabel>{label}</StepLabel>
          </Step>
        ))}
      </Stepper>
      <form onSubmit={handleFinalSubmit} style={{ padding: "24px 0" }}>
        {activeStep === 0 && <Step1Form />}
        {activeStep === 1 && <Step2Form />}
        <div style={{ marginTop: "24px", display: "flex", gap: "12px" }}>
          <Button disabled={activeStep === 0} onClick={handleBack}>上一步</Button>
          {activeStep < steps.length - 1 && (
            <Button variant="contained" onClick={handleNext}>下一步</Button>
          )}
          {activeStep === steps.length - 1 && (
            <Button variant="contained" type="submit">提交</Button>
          )}
        </div>
      </form>
    </FormProvider>
  );
}

步骤1子组件代码

import { useFormContext, Controller } from "react-hook-form";
import TextField from "@mui/material/TextField";

export default function Step1Form() {
  const { control } = useFormContext();
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
      <Controller
        name="step1.firstname" // 对应嵌套字段路径
        control={control}
        rules={{ required: "请填写名字" }}
        render={({ field, fieldState }) => (
          <TextField 
            {...field} 
            label="名字" 
            fullWidth
            error={!!fieldState.error}
            helperText={fieldState.error?.message}
          />
        )}
      />
      <Controller
        name="step1.lastname"
        control={control}
        rules={{ required: "请填写姓氏" }}
        render={({ field, fieldState }) => (
          <TextField 
            {...field} 
            label="姓氏" 
            fullWidth
            error={!!fieldState.error}
            helperText={fieldState.error?.message}
          />
        )}
      />
    </div>
  );
}

步骤2子组件代码

import { useFormContext, Controller } from "react-hook-form";
import TextField from "@mui/material/TextField";

export default function Step2Form() {
  const { control } = useFormContext();
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
      <Controller
        name="step2.address"
        control={control}
        rules={{ required: "请填写详细地址" }}
        render={({ field, fieldState }) => (
          <TextField 
            {...field} 
            label="详细地址" 
            fullWidth
            error={!!fieldState.error}
            helperText={fieldState.error?.message}
          />
        )}
      />
      <Controller
        name="step2.city"
        control={control}
        rules={{ required: "请填写所在城市" }}
        render={({ field, fieldState }) => (
          <TextField 
            {...field} 
            label="城市" 
            fullWidth
            error={!!fieldState.error}
            helperText={fieldState.error?.message}
          />
        )}
      />
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 11:27:02