React でバリデーション付きのモーダルを出したい

データを確認したり、更新するためのUIとして、下図のような感じでバリデーション付きのモーダルが欲しいと思いました。でも自分でイチからは作りたくない。そして、複数のライブラリを組み合わせることもしたくないと思いました。

モーダルでデータを更新

そうしたら、よさそうなのがありました。Ant Design です。上の図はそれで試してみた結果です。

コード

次のようにやりました(Code Sandobox参照)。

import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Button, Modal, Form, Input, Radio } from "antd";

const MemberEditForm = ({ visible, member, onCreate, onCancel }) => {
  const [form] = Form.useForm();
  return (
    <Modal
      visible={visible}
      title="メンバー情報の編集"
      okText="更新"
      cancelText="キャンセル"
      onCancel={onCancel}
      onOk={() => {
        form
          .validateFields()
          .then((values) => {
            onCreate(values);
          })
          .catch((info) => console.log("バリデーション失敗:", info));
      }}
    >
      <Form
        form={form}
        layout="vertical"
        name="form_in_modal"
        initialValues={{
          name: member.name,
          title: member.title,
          bio: member.bio,
          email: member.email,
          canSee: member.canSee
        }}
      >
        <Form.Item
          name="name"
          label="名前"
          rules={[
            {
              required: true,
              message: "名前を入力してください。"
            }
          ]}
        >
          <Input />
        </Form.Item>
        <Form.Item
          name="title"
          label="タイトル"
          rules={[
            {
              required: true,
              message: "タイトルを入力してください。"
            }
          ]}
        >
          <Input />
        </Form.Item>
        <Form.Item name="bio" label="プロフィール">
          <Input.TextArea autoSize="true" />
        </Form.Item>
        <Form.Item
          name="email"
          label="メールアドレス"
          rules={[
            {
              required: false,
              type: "email",
              message: "Eメールアドレスとして適切なものを入力してください。"
            }
          ]}
        >
          <Input placeholder="abc@pitang1965.com" />
        </Form.Item>
        <Form.Item name="canSee" className="member-edit-form_last-form-item">
          <Radio.Group>
            <Radio value="public">公開</Radio>
            <Radio value="private">非公開</Radio>
          </Radio.Group>
        </Form.Item>
      </Form>
    </Modal>
  );
};

const MemberPage = () => {
  const [visible, setVisible] = useState(false);
  const [member, setMember] = useState({
    name: "yoko",
    title: "浪人",
    bio:
      "Web、ナレーション、動画制作であなたにウフ 💓 を。イベント盛り上げ隊!",
    email: "yoko@abc.com",
    canSee: "private"
  });

  const onCreate = (values) => {
    console.log("Received values of form: ", values);
    setVisible(false);
    setMember((prev) => ({ ...prev, ...values }));
  };

  return (
    <div>
      <h1>{member.name}</h1>
      <p>タイトル: {member.title}</p>
      <p>プロフィール: {member.bio}</p>
      <p>メールアドレス: {member.email}</p>
      <p>{member.canSee === "public" ? "公開" : "非公開"}</p>
      <Button type="primary" onClick={() => setVisible(true)}>
        編集
      </Button>
      <MemberEditForm
        visible={visible}
        member={member}
        onCreate={onCreate}
        onCancel={() => {
          setVisible(false);
        }}
      />
    </div>
  );
};

ReactDOM.render(<MemberPage />, document.getElementById("container"));

躓いたポイント

初期値の与え方

各フィールド(例:名前、メールアドレス)は、<Form.Item>内の<Input>defaultValue プロパティで与えることができるのですが、警告が出ます。<Form>内で使うには、次のように inialValues プロパティを使う必要がありました。

<Form
  form={form}
  layout="vertical"
  name="form_in_modal"
  initialValues={{
    name: member.name,
    title: member.title,
    bio: member.bio,
    email: member.email,
    canSee: member.canSee ? "public" : "private"
  }}
>

useStateの使い方

モーダルで変更された値を反映されるには次のようにします。

const [member, setMember] = useState({
  name: "yoko",
  title: "浪人",
  bio:
    "Web、ナレーション、動画制作であなたにウフ 💓 を。イベント盛り上げ隊!",
  email: "yoko@abc.com",
  canSee: "private"
});

:
:

const onCreate = (values) => {
  console.log("Received values of form: ", values);
  setVisible(false);   
  setMember((prev) => ({ ...prev, ...values }));  <----- ★これ!
};

再度モーダルが表示されるときの初期値

下記コメントアウトしたコードがあると、1回目に表示されるときの値が表示されてしまいます。

const MemberEditForm = ({ visible, member, onCreate, onCancel }) => {
  const [form] = Form.useForm();
  return (
    <Modal
      visible={visible}
      title="メンバー情報の編集"
      okText="更新"
      cancelText="キャンセル"
      onCancel={onCancel}
      onOk={() => {
        form
          .validateFields()
          .then((values) => {
            form.resetFields();  <----- ★これ!
            onCreate(values);
          })
          .catch((info) => console.log("バリデーション失敗:", info));
      }}
    >

詳細は詳しいドキュメントに書かれています。