概要
React Hook Formで、配列形式で入力内容を管理したい場合に使用するのがuseFieldArray
になります。
ケースとしてはあまりないかもしれませんが、useFieldArrayの配下で別の配列項目でuseFieldArrayを使用したい場合どう実装するのか、メモ書きします。
前提
- 今回使用したReact Hook Formのバージョンは
7.51.4
です。
対応方法
React Hook Form - useFieldArray nested arraysの実装サンプルにある通り、親配列と子配列を別コンポーネントにする対応が挙げられます。子のコンポーネントでは、親配列のindexを受け取り自身のuseFieldArrayに設定する形になります。
実装サンプル
上記の実装サンプルを参考にして、私の方で実装したものが以下になります。
まずはformのschemaです。zodを用いて以下のように定義します。
export const voteResultInputFormSchema = z.object({
voteRaceList: z.array(
z.object({
raceId: z.string(),
voteRaceContents: z.array(
z.object({
contents: z.string().optional(),
})
),
})
),
});
以下は親配列を設定するコンポーネントです。
子のコンポーネントに親のindexを渡します。
export const RegisterVoteInputComponent: FC<Props> = ({ raceInfoList }) => {
const form = useForm<z.infer<typeof voteResultInputFormSchema>>({
resolver: zodResolver(voteResultInputFormSchema),
mode: "onSubmit",
reValidateMode: "onSubmit",
});
const control = form.control;
const {
fields: voteRaceFields,
prepend: voteRacePrepend,
remove: voteRaceRemove,
} = useFieldArray({
control,
name: "voteRaceList",
});
return (
<Form {...form}>
<form>
<div className="flex flex-col gap-4 ml-2">
{voteRaceFields.length < raceInfoList.length && (
<div>
<Button
className={buttonStyle({ color: "lime" })}
onClick={() => {
voteRacePrepend({});
}}
type="button"
>
レースを追加
</Button>
</div>
)}
{voteRaceFields.map((race, index) => {
return (
<Card key={race.id}>
<CardContent className="p-2 min-w-[350px]">
<div className="flex gap-4">
<FormField
control={form.control}
name={`voteRaceList.${index}.raceId`}
render={({ field }) => (
<FormItem className="min-w-[200px]">
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="レースを選択" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="">レースを選択</SelectItem>
{raceInfoList.map((r) => {
return (
<SelectItem key={r.id} value={r.id}>
{r.raceName}
</SelectItem>
);
})}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
{voteRaceFields.length > 1 && (
<div>
<Button
className={`${buttonStyle({
color: "gray",
})} w-18`}
onClick={() => {
voteRaceRemove(index);
}}
type="button"
>
レースを削除
</Button>
</div>
)}
</div>
<div className="mt-2">
{/* 子のコンポーネントにindexを渡す */}
<RegisterVoteContentsInputComponent
control={form.control}
raceIndex={index}
/>
</div>
</CardContent>
</Card>
);
})}
</div>
</form>
</Form>
);
};
以下は子配列を設定するコンポーネントです。
親から受け取ったindexをuseFieldArrayに設定します。
export const RegisterVoteContentsInputComponent: FC<Props> = ({
control,
raceIndex,
}) => {
// 親から受け取ったindexを設定してuseFieldArrayを使用
const {
fields: voteRaceContentFields,
prepend: voteRaceContentPrepend,
remove: voteRaceContentRemove,
} = useFieldArray({
control,
name: `voteRaceList.${raceIndex}.voteRaceContents`,
});
return (
<div>
<Button
className={`${buttonStyle({ color: "yellow" })} mb-3`}
onClick={() => {
voteRaceContentPrepend({});
}}
type="button"
>
内容追加
</Button>
{voteRaceContentFields.map((c, index) => (
<div className="flex-col gap-3" key={c.id}>
<FormField
control={control}
name={`voteRaceList.${raceIndex}.voteRaceContents.${index}.contents`}
render={({ field }) => (
<FormItem>
<FormLabel>内容</FormLabel>
<FormControl>
<Textarea {...field} className={inputTextAreaStyle()} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{voteRaceContentFields.length > 1 && (
<div className="mt-4 mb-2">
<Button
className={`${buttonStyle({ color: "gray" })} w-28`}
onClick={() => {
voteRaceContentRemove(index);
}}
type="button"
>
内容を削除
</Button>
</div>
)}
</div>
))}
</div>
);
};