2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React】React Hook FormのuseFieldArrayを入れ子で使いたい

Posted at

概要

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>
  );
};

その他参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?