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

Remix-run中基于Zod实现表单联动Footer按钮的启用禁用与提交

Remix + Zod 实现表单联动提交与状态同步方案

实现步骤

1. 父布局传递状态控制与提交引用

修改OnboardLayout,添加用于存储子表单提交方法的ref,并通过Outlet上下文传递状态更新函数和该ref:

import { useState, useRef } from "react";
import { Outlet } from "@remix-run/react";

export default function OnboardLayout(){
  const [currentPage, setCurrentPage] = useState(1);
  const [filled, setFilled] = useState(false);
  const formSubmitRef = useRef(null);

  // 切换页面时重置填充状态,避免跨页状态污染
  const handlePageChange = (newPage) => {
    setCurrentPage(newPage);
    setFilled(false);
  };

  return (
    <div>
      <OnboardNavBar page={currentPage}/>
      <div className="flex justify-center">
        <Outlet context={{ setFilled, formSubmitRef }} />
      </div>
      <OnboardFooter 
        page={currentPage} 
        onPageClick={handlePageChange} 
        filled={filled} 
        triggerSubmit={() => formSubmitRef.current?.()}
      />
    </div>
  );
}

2. 子表单页面实现实时验证与提交方法暴露

每个由Outlet渲染的表单页面,用Zod做实时字段验证,将验证结果同步到父布局的filled状态,并把表单提交方法挂载到父布局传递的ref上:

import { useOutletContext, useSubmit } from "@remix-run/react";
import { useState, useEffect } from "react";
import { z } from "zod";

// 定义当前步骤的Zod验证规则
const StepSchema = z.object({
  username: z.string().min(3, "用户名至少3位"),
  email: z.string().email("邮箱格式不正确"),
});

export default function OnboardStep1() {
  const { setFilled, formSubmitRef } = useOutletContext();
  const [formData, setFormData] = useState({ username: "", email: "" });
  const submit = useSubmit(); // Remix内置提交钩子

  // 实时验证表单并同步状态到父布局
  useEffect(() => {
    const validationResult = StepSchema.safeParse(formData);
    setFilled(validationResult.success);
  }, [formData, setFilled]);

  // 将提交方法挂载到父组件的ref上
  useEffect(() => {
    formSubmitRef.current = handleSubmit;
    return () => {
      formSubmitRef.current = null; // 卸载时清空ref
    };
  }, []);

  const handleChange = (e) => {
    setFormData(prev => ({ ...prev, [e.target.name]: e.target.value }));
  };

  const handleSubmit = () => {
    // 使用Remix的useSubmit提交表单,或自定义提交逻辑
    submit(formData, { method: "post", action: "/onboard/step1" });
  };

  return (
    <form>
      <input
        type="text"
        name="username"
        value={formData.username}
        onChange={handleChange}
        placeholder="用户名"
      />
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="邮箱"
      />
    </form>
  );
}

3. Footer组件实现按钮状态控制与提交触发

修改OnboardFooter,根据filled状态控制按钮禁用,点击时调用父布局传递的提交触发函数:

export default function OnboardFooter({ page, onPageClick, filled, triggerSubmit }) {
  const isFinalPage = page === 3; // 根据实际步骤数调整

  return (
    <div className="flex justify-between p-4">
      {page > 1 && (
        <button onClick={() => onPageClick(page - 1)}>上一页</button>
      )}
      <button
        type="button"
        onClick={triggerSubmit}
        disabled={!filled}
        className={`px-4 py-2 rounded ${filled ? "bg-blue-600 text-white" : "bg-gray-300 cursor-not-allowed"}`}
      >
        {isFinalPage ? "提交表单" : "继续"}
      </button>
    </div>
  );
}

关键注意事项

  • 每个表单步骤都需要重复子页面的验证与提交逻辑,确保filled状态和提交方法正确同步
  • 若使用Remix的action处理表单提交,需确保每个步骤的action路径正确,避免提交到错误路由
  • 切换页面时重置filled状态,防止上一页的验证结果影响当前页的按钮状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 04:42:51