Next.js13+TS中如何在Formik的Form内使用两个FieldArray?
你之前对FieldArray的用法误解了——它完全可以放在Form标签内部,不需要把整个Form包裹进去。每个需要数组类型的字段单独用FieldArray对应处理就行,同时支持添加、删除操作,具体修改步骤如下:
1. 修正初始值与TypeScript类型
首先把allergiesName和medicalConditionsName从字符串改成空数组,并定义对应TS类型确保类型安全:
type MedicalFormValues = { allergiesName: string[]; medicalConditionsName: string[]; emergencyContactName: string; emergencyContactPhone: string; }; // Formik初始值 initialValues: MedicalFormValues = { allergiesName: [], medicalConditionsName: [], emergencyContactName: "", emergencyContactPhone: "", }
2. 调整FieldArray的正确用法
每个FieldArray需要接收一个渲染函数,用来获取push(添加项)、remove(删除项)等操作方法,然后遍历数组渲染输入框,同时添加操作按钮。以allergiesName为例:
<FieldArray name="allergiesName"> {({ push, remove }) => ( <div className="mb-4"> <label className="text-[12px] font-bold block mb-1">Allergies</label> {/* 遍历现有数组项渲染输入框 */} {values.allergiesName.map((_, index) => ( <div key={index} className="flex gap-2 mb-2"> <Field required type="text" name={`allergiesName[${index}]`} className="rounded-[10px] inputFieldBg border-0 text-sm focus:ring-blue-500 focus:border-blue-500 block flex-1 p-2.5" autoComplete="additional-name" /> <button type="button" onClick={() => remove(index)} className="text-red-500 self-center" > 删除 </button> </div> ))} {/* 添加新项按钮 */} <button type="button" onClick={() => push("")} className="text-blue-500 text-sm mt-1" > + 添加过敏项 </button> </div> )} </FieldArray>
medicalConditionsName字段做完全相同的修改即可。
3. 更新验证Schema
如果使用Yup做表单验证,需要把两个数组字段的规则改成数组类型:
import * as Yup from 'yup'; const SignUpSchema = Yup.object().shape({ allergiesName: Yup.array() .of(Yup.string().required("过敏项不能为空")) .min(1, "至少添加一个过敏项"), // 可选,根据业务需求调整 medicalConditionsName: Yup.array() .of(Yup.string().required("病史项不能为空")) .min(1, "至少添加一个病史项"), emergencyContactName: Yup.string().required("紧急联系人姓名不能为空"), emergencyContactPhone: Yup.string().required("紧急联系人电话不能为空"), });
完整修改后的代码(Next.js 13 AppRouter适配)
注意添加'use client'指令,因为Formik是客户端组件:
'use client'; import { Formik, Form, Field, FieldArray } from 'formik'; import * as Yup from 'yup'; type MedicalFormValues = { allergiesName: string[]; medicalConditionsName: string[]; emergencyContactName: string; emergencyContactPhone: string; }; const SignUpSchema = Yup.object().shape({ allergiesName: Yup.array() .of(Yup.string().required("过敏项不能为空")) .min(1, "至少添加一个过敏项"), medicalConditionsName: Yup.array() .of(Yup.string().required("病史项不能为空")) .min(1, "至少添加一个病史项"), emergencyContactName: Yup.string().required("紧急联系人姓名不能为空"), emergencyContactPhone: Yup.string().required("紧急联系人电话不能为空"), }); export default function MedicalForm() { return ( <Formik validationSchema={SignUpSchema} initialValues={ { allergiesName: [], medicalConditionsName: [], emergencyContactName: "", emergencyContactPhone: "", } as MedicalFormValues } onSubmit={(values) => { // 提交时values中的两个字段已为数组类型 console.log('提交数据:', values); }} > {({ values }) => ( <div className="text-white w-[90%] formRegisterBg self-center flex flex-col px-4 pt-2 pb-6 rounded-[15px] form1"> <span className="text-[20px] font-bold mt-2 mb-3">Medical Information</span> <Form> {/* 过敏项数组 */} <FieldArray name="allergiesName"> {({ push, remove }) => ( <div className="mb-4"> <label className="text-[12px] font-bold block mb-1">Allergies</label> {values.allergiesName.map((_, index) => ( <div key={index} className="flex gap-2 mb-2"> <Field required type="text" name={`allergiesName[${index}]`} className="rounded-[10px] inputFieldBg border-0 text-sm focus:ring-blue-500 focus:border-blue-500 block flex-1 p-2.5" autoComplete="additional-name" /> <button type="button" onClick={() => remove(index)} className="text-red-500 self-center" > 删除 </button> </div> ))} <button type="button" onClick={() => push("")} className="text-blue-500 text-sm mt-1" > + 添加过敏项 </button> </div> )} </FieldArray> {/* 病史项数组 */} <FieldArray name="medicalConditionsName"> {({ push, remove }) => ( <div className="mb-4"> <label className="text-[12px] font-bold block mb-1">Medical Conditions</label> {values.medicalConditionsName.map((_, index) => ( <div key={index} className="flex gap-2 mb-2"> <Field required type="text" name={`medicalConditionsName[${index}]`} className="rounded-[10px] inputFieldBg border-0 text-sm focus:ring-blue-500 focus:border-blue-500 block flex-1 p-2.5" autoComplete="additional-name" /> <button type="button" onClick={() => remove(index)} className="text-red-500 self-center" > 删除 </button> </div> ))} <button type="button" onClick={() => push("")} className="text-blue-500 text-sm mt-1" > + 添加病史项 </button> </div> )} </FieldArray> {/* 紧急联系人姓名 */} <div className="mb-4"> <label htmlFor="emergencyContactName" className="text-[12px] font-bold block mb-1" > Emergency Contact Name </label> <Field required type="text" id="emergencyContactName" name="emergencyContactName" className="rounded-[10px] inputFieldBg border-0 text-sm focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" autoComplete="additional-name" /> </div> {/* 紧急联系人电话 */} <div className="mb-4"> <label htmlFor="emergencyContactPhone" className="text-[12px] font-bold block mb-1" > Emergency Contact Phone </label> <Field required type="text" id="emergencyContactPhone" name="emergencyContactPhone" className="rounded-[10px] inputFieldBg border-0 text-sm focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" autoComplete="additional-name" /> </div> {/* 提交按钮 */} <button type="submit" className="bg-blue-500 text-white rounded-[10px] p-2.5 mt-2" > 提交 </button> </Form> </div> )} </Formik> ); }
关键说明
- FieldArray可以直接嵌入Form内部,每个数组字段单独用一个FieldArray包裹对应的输入区域即可,无需嵌套整个Form。
- 渲染函数中的
push方法用于向数组添加新的空输入项,remove方法用于删除指定索引的项。 - Field的name属性必须写成
数组名[索引]的格式,Formik才能正确绑定数组中的每一个元素。 - TypeScript类型定义确保初始值与表单值的类型一致,避免类型错误。
内容的提问来源于stack exchange,提问作者Inzen
相关产品推荐
相关产品推荐

