0
0

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-hook-formを使ってdynamic-inputを実装する

Posted at

react-hook-formのuseFieldArrayを使ってフォーム内の要素が動的に変化するフォームを作成しようと思います。
またフォームの初期値にはapiから取得したレスポンスを設定します。

環境

@mui/material 5.15.13
next 14.1.3
react 18
swr 2.2.5
zod 3.23.6

フォームの定義

作成するフォームでは名前、メールアドレス、電話番号を入力するのでzodを使って下記のように定義します。

const formValueSchema = z.object({
  users: z.array(
    z.object({
      name: z.string().min(1, { message: "名前を入力してください" }),
      email: z.string().min(1, { message: "メールアドレスを入力してください" }),
      phone: z.string().min(1, { message: "電話番号を入力してください" }),
        })
  ),
});
type FormValue = z.infer<typeof formValueSchema>;

ユーザー取得

次にjsonplaceholderからユーザー情報を取得します。

const Page: NextPageWithLayout = () => {
  const { data, error, isLoading } = useSWR<User[]>(
    "https://jsonplaceholder.typicode.com/users",
    fetcher
  );

  return <></>;
};

Page.getLayout = (page: ReactElement) => {
  return <AuthLayout>{page}</AuthLayout>;
};

export default Page;

フォーム制御

フォームの状態管理のために必要な定数をuseForm, useFieldarrayから取得します。
またuseEffectを使ってフォームの初期値にAPIからのレスポンスを設定しています。

const formValueSchema = z.object({
  users: z.array(
    z.object({
      name: z.string().min(1, { message: "名前を入力してください" }),
      email: z.string().min(1, { message: "メールアドレスを入力してください" }),
      phone: z.string().min(1, { message: "電話番号を入力してください" }),
    })
  ),
});
type FormValue = z.infer<typeof formValueSchema>;

const Page: NextPageWithLayout = () => {
  const { data, error, isLoading } = useSWR<User[]>(
    "https://jsonplaceholder.typicode.com/users",
    fetcher
  );

  const defaultValue = {
    name: "",
    email: "",
    phone: "",
  };

  const {
    control,
    handleSubmit,
    reset,
    formState: { isValid },
  } = useForm<FormValue>({
    resolver: zodResolver(formValueSchema),
    defaultValues: {
      users: [defaultValue],
    },
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: "users",
  });

  useEffect(() => {
    if (data) {
      reset({
        users: data.map((user) => ({
          name: user.name,
          email: user.email,
          phone: user.phone,
        })),
      });
    }
  }, [reset, data]);

  const onSubmit = (formValue: FormValue) => {
    console.log(formValue);
  };

  return <></>;
};

Page.getLayout = (page: ReactElement) => {
  return <AuthLayout>{page}</AuthLayout>;
};

export default Page;

フォーム表示

初期化したデータを元にフォームを構築します。

const formValueSchema = z.object({
  users: z.array(
    z.object({
      name: z.string().min(1, { message: "名前を入力してください" }),
      email: z.string().min(1, { message: "メールアドレスを入力してください" }),
      phone: z.string().min(1, { message: "電話番号を入力してください" }),
    })
  ),
});
type FormValue = z.infer<typeof formValueSchema>;

const Page: NextPageWithLayout = () => {
  const { data, error, isLoading } = useSWR<User[]>(
    "https://jsonplaceholder.typicode.com/users",
    fetcher
  );

  const defaultValue = {
    name: "",
    email: "",
    phone: "",
  };

  const {
    control,
    handleSubmit,
    reset,
    formState: { isValid },
  } = useForm<FormValue>({
    resolver: zodResolver(formValueSchema),
    defaultValues: {
      users: [defaultValue],
    },
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: "users",
  });

  useEffect(() => {
    if (data) {
      reset({
        users: data.map((user) => ({
          name: user.name,
          email: user.email,
          phone: user.phone,
        })),
      });
    }
  }, [reset, data]);

  const onSubmit = (formValue: FormValue) => {
    console.log(formValue);
  };

  if (error) {
    return <div>failed to load</div>;
  }

  if (isLoading || !data) {
    return <div>loading...</div>;
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TableContainer>
        <Table>
          <TableHead>
            <TableRow>
              <TableCell>
                <Typography>Name</Typography>
              </TableCell>
              <TableCell>
                <Typography>Email</Typography>
              </TableCell>
              <TableCell>
                <Typography>Phone</Typography>
              </TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {fields.map((field, index) => (
              <TableRow key={field.id}>
                <TableCell>
                  <Controller
                    name={`users.${index}.name`}
                    control={control}
                    render={({ field, fieldState }) => (
                      <TextField
                        {...field}
                        error={fieldState.invalid}
                        helperText={fieldState.error?.message}
                        fullWidth
                      />
                    )}
                  />
                </TableCell>
                <TableCell>
                  <Controller
                    name={`users.${index}.email`}
                    control={control}
                    render={({ field, fieldState }) => (
                      <TextField
                        {...field}
                        error={fieldState.invalid}
                        helperText={fieldState.error?.message}
                        fullWidth
                      />
                    )}
                  />
                </TableCell>
                <TableCell>
                  <Controller
                    name={`users.${index}.phone`}
                    control={control}
                    render={({ field, fieldState }) => (
                      <TextField
                        {...field}
                        error={fieldState.invalid}
                        helperText={fieldState.error?.message}
                        fullWidth
                      />
                    )}
                  />
                </TableCell>
                <TableCell>
                  <Button variant="outlined" onClick={() => remove(index)}>
                    delete
                  </Button>
                </TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      </TableContainer>
      <Button variant="contained" onClick={() => append(defaultValue)}>
        add
      </Button>
      <Button variant="contained" type="submit">
        save
      </Button>
    </form>
  );
};

Page.getLayout = (page: ReactElement) => {
  return <AuthLayout>{page}</AuthLayout>;
};

export default Page;

参考

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?