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
相关产品推荐
相关产品推荐

