跳至内容

useFieldArray

React hooks for Field Array

useFieldArray UseFieldArrayProps

用于处理字段数组(动态表单)的自定义钩子。其目的是为了提供更好的用户体验和性能。你可以观看 这段简短的视频 来直观感受性能的提升。

属性

名称类型必填描述
name字符串

字段数组的名称。注意:不支持动态名称。

control对象control useForm 提供的对象。如果使用 FormProvider,则可选。
shouldUnregister布尔值

卸载后是否注销字段数组。

keyName字符串 = id

用于作为 key 属性的自动生成标识符的属性名称。此属性不再需要,将在下一个主要版本中删除。

rules对象

register 相同的验证 rules API,包括

required, minLength, maxLength, validate

useFieldArray({
rules: { minLength: 4 }
})

如果出现验证错误,则 root 属性将追加到 formState.errors?.fieldArray?.root 类型为 FieldError

重要:这仅适用于内置验证。

示例

function FieldArray() {
const { control, register } = useForm();
const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({
control, // control props comes from useForm (optional: if you are using FormProvider)
name: "test", // unique name for your Field Array
});
return (
{fields.map((field, index) => (
<input
key={field.id} // important to include key with field's id
{...register(`test.${index}.value`)}
/>
))}
);
}

返回

名称类型描述
fields对象 &{ id: 字符串 }object 包含 defaultValue key 用于你的组件。
append(obj: object | object[], focusOptions) => void

将输入/输入追加到字段的末尾并聚焦。输入值将在执行此操作期间注册。

重要:需要追加数据,不能是部分数据。

prepend(obj: object | object[], focusOptions) => void

将输入/输入添加到字段的开头并聚焦。输入值将在执行此操作期间注册。

重要:需要添加数据,不能是部分数据。

insert(index: number, value: object | object[], focusOptions) => void

在特定位置插入输入/输入并聚焦。

重要:需要插入数据,不能是部分数据。

swap(from: number, to: number) => void交换输入/输入位置。
move(from: number, to: number) => void将输入/输入移动到另一个位置。
update(index: number, obj: object) => void

更新特定位置的输入/输入,更新的字段将被卸载并重新安装。如果不需要此行为,请使用 setValue API 代替。

重要:需要更新数据,不能是部分数据。

replace(obj: object[]) => void替换整个字段数组的值。
remove(index?: number | number[]) => void删除特定位置的输入/输入,或在未提供索引时删除所有输入/输入。

规则

  • useFieldArray 自动生成一个名为 id 的唯一标识符,用于 key属性。有关为什么需要此操作的更多信息,请参阅 https://reactjs.ac.cn/learn/rendering-lists

    必须将 field.id(而不是 index)添加为组件键,以防止重新渲染破坏字段

    // ✅ correct:
    {fields.map((field, index) => <input key={field.id} ... />)}
    // ❌ incorrect:
    {fields.map((field, index) => <input key={index} ... />)}

  • 建议不要将操作一个接一个地堆叠。

    onClick={() => {
    append({ test: 'test' });
    remove(0);
    }}
    // ✅ Better solution: the remove action is happened after the second render
    React.useEffect(() => {
    remove(0);
    }, [remove])
    onClick={() => {
    append({ test: 'test' });
    }}
  • 每个 useFieldArray 都是唯一的,并且具有自己的状态更新,这意味着不应该有多个使用相同 name 的 useFieldArray。

  • 每个输入名称都需要是唯一的,如果你需要构建具有相同名称的复选框或单选按钮,请将其与 useControllerController 一起使用。

  • 不支持扁平字段数组。

  • 当你追加、添加、插入和更新字段数组时,obj 不能是空对象 ,而需要提供所有输入的 defaultValues。

    append();
    append({});
    append({ firstName: 'bill', lastName: 'luo' });

TypeScript

  • 在注册输入 name 时,你将不得不将其强制转换为 const

    <input key={field.id} {...register(`test.${index}.test` as const)} />
  • 我们不支持循环引用。请参阅此 Github 问题 以获取更多详细信息。

  • 对于嵌套字段数组,你将不得不根据其名称强制转换字段数组。

    const { fields } = useFieldArray({ name: `test.${index}.keyValue` as 'test.0.keyValue' });

示例

CodeSandbox JS
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
function App() {
const { register, control, handleSubmit, reset, trigger, setError } = useForm({
// defaultValues: {}; you can populate the fields by this attribute
});
const { fields, append, remove } = useFieldArray({
control,
name: "test"
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<ul>
{fields.map((item, index) => (
<li key={item.id}>
<input {...register(`test.${index}.firstName`)} />
<Controller
render={({ field }) => <input {...field} />}
name={`test.${index}.lastName`}
control={control}
/>
<button type="button" onClick={() => remove(index)}>Delete</button>
</li>
))}
</ul>
<button
type="button"
onClick={() => append({ firstName: "bill", lastName: "luo" })}
>
append
</button>
<input type="submit" />
</form>
);
}
import * as React from "react";
import { useForm, useFieldArray, useWatch } from "react-hook-form";
export default function App() {
const { control, handleSubmit } = useForm();
const { fields, append, update } = useFieldArray({
control,
name: 'array'
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
{fields.map((field, index) => (
<Edit
key={field.id}
control={control}
update={update}
index={index}
value={field}
/>
))}
<button
type="button"
onClick={() => {
append({ firstName: "" });
}}
>
append
</button>
<input type="submit" />
</form>
);
}
const Display = ({ control, index }) => {
const data = useWatch({
control,
name: `array.${index}`
});
return <p>{data?.firstName}</p>;
};
const Edit = ({ update, index, value, control }) => {
const { register, handleSubmit } = useForm({
defaultValues: value
});
return (
<div>
<Display control={control} index={index} />
<input
placeholder="first name"
{...register(`firstName`, { required: true })}
/>
<button
type="button"
onClick={handleSubmit((data) => update(index, data))}
>
Submit
</button>
</div>
);
};
import React from 'react';
import { useForm, useWatch, useFieldArray, Control } from 'react-hook-form';
type FormValues = {
data: { name: string }[];
};
const ConditionField = ({
control,
index,
register,
}: {
control: Control<FormValues>;
index: number;
}) => {
const output = useWatch({
name: 'data',
control,
defaultValue: 'yay! I am watching you :)',
});
return (
<>
{output[index]?.name === "bill" && (
<input {...register(`data[${index}].conditional`)} />
)}
<input
{...register(`data[${index}].easyConditional`)}
style={{ display: output[index]?.name === "bill" ? "block" : "none" }}
/>
</>
);
};
const UseFieldArrayUnregister: React.FC = () => {
const { control, handleSubmit, register } = useForm<FormValues>({
defaultValues: {
data: [{ name: 'test' }, { name: 'test1' }, { name: 'test2' }],
},
mode: 'onSubmit',
shouldUnregister: false,
});
const { fields } = useFieldArray({
control,
name: 'data',
});
const onSubmit = (data: FormValues) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{fields.map((data, index) => (
<>
<input {...register(`data[${index}].name`)} />
<ConditionField control={control} register={register} index={index} />
</>
))}
<input type="submit" />
</form>
);
};
import React from 'react';
import { useForm, useFieldArray } from 'react-hook-form';
const App = () => {
const { register, control } = useForm<{
test: { value: string }[];
}>({
defaultValues: {
test: [{ value: '1' }, { value: '2' }],
},
});
const { fields, prepend, append } = useFieldArray({
name: 'test',
control,
});
return (
<form>
{fields.map((field, i) => (
<input key={field.id} {...register(`test.${i}.value` as const)} />
))}
<button
type="button"
onClick={() => prepend({ value: '' }, { focusIndex: 1 })}
>
prepend
</button>
<button
type="button"
onClick={() => append({ value: '' }, { focusName: 'test.0.value' })}
>
append
</button>
</form>
);
};

视频

以下视频解释了 useFieldArray.

技巧

自定义注册

你也可以在 Controllerregister 输入而无需实际的输入。这使得 useFieldArray 在处理复杂数据结构或实际数据未存储在输入中时使用起来快速灵活。

import { useForm, useFieldArray, Controller, useWatch } from "react-hook-form";
const ConditionalInput = ({ control, index, field }) => {
const value = useWatch({
name: "test",
control
});
return (
<Controller
control={control}
name={`test.${index}.firstName`}
render={({ field }) =>
value?.[index]?.checkbox === "on" ? <input {...field} /> : null
}
/>
);
};
function App() {
const { control, register } = useForm();
const { fields, append, prepend } = useFieldArray({
control,
name: "test"
});
return (
<form>
{fields.map((field, index) => (
<ConditionalInput key={field.id} {...{ control, index, field }} />
))}
</form>
);
}

受控字段数组

在某些情况下,你可能希望控制整个字段数组,这意味着每个 onChange 都会反映在 fields 对象中。

import { useForm, useFieldArray } from "react-hook-form";
export default function App() {
const { register, handleSubmit, control, watch } = useForm<FormValues>();
const { fields, append } = useFieldArray({
control,
name: "fieldArray"
});
const watchFieldArray = watch("fieldArray");
const controlledFields = fields.map((field, index) => {
return {
...field,
...watchFieldArray[index]
};
});
return (
<form>
{controlledFields.map((field, index) => {
return <input {...register(`fieldArray.${index}.name` as const)} />;
})}
</form>
);
}

感谢你的支持

如果你发现 React Hook Form 在你的项目中很有用,请考虑为其加星并支持它。