11
4

More than 1 year has passed since last update.

React Final Form #01 かんたんにフォームを構築する

Last updated at Posted at 2021-05-31

この記事を読むと幸せになれそうな人

React.useState や Redux を使う方法で大きめのフォームを構築するとき、UIライブラリを使ったり、バリデーションを実装したりすると、ボイラープレートコードが多すぎて辟易してしまうと思います。
そんな人には、この React Final Form がオススメかもしれません。

(個人的には React Hooks Form よりもこっちの方が明示的で好きです...)

今回やること

今回はとりあえず、フォームを構築して、送信処理を実装するところまでを実装します。

1. Formコンポーネントを設置する。

当記事では、テスト用のページを手早く用意するために Next.js を、使い慣れているので Typescript を使っていますが、内容には特に関係ありません。
テストする場合はそれぞれのやりやすい環境でどうぞ。

まずは、中身も空っぽ、送信時に何もしないフォームを作成してみましょう。

pages/react-final-forms/index.tsx
const ReactFinalFormPage: NextPage = () => {
  const handleSubmit: FormProps["onSubmit"] = (s) => {
    /* 処理の中身からっぽ */
  };
  return (
    <Form
      onSubmit={handleSubmit}
      render={({ handleSubmit }) => (
        <form onSubmit={handleSubmit}>
          <div>からっぽのフォームです</div>
        </form>
      )}
    />
  );
};
export default ReactFinalFormPage;

<Form>コンポーネント は、必須として、onSubmit prop を受け取ります。
( asyncな関数も渡すことが出来ます。)

その次には、 render: (props: FormRenderProps) => ... prop にレンダー関数を渡しています。

FormRenderProps から分割代入で取り出した handleSubmit 関数を、内側の <form/>要素の onSubmit prop に渡すことになっています。

(言わずもがな、 const宣言されている handleSubmit とは別です。)

2.送信ボタンを追加する

それでは、とにかくダミーの送信処理と、送信ボタンを追加してみましょう。

pages/react-final-forms/index.tsx
+ const waitMs = (ms: number) =>
+    new Promise((resolve) => setTimeout(resolve, ms));
  
  const ReactFinalFormPage: NextPage = () => {
    const handleSubmit: FormProps["onSubmit"] = async (s) => {
+     await waitMs(1000);
+     window.alert("送信されました!");
    };
    return (
      <Form
        onSubmit={handleSubmit}
        render={({ handleSubmit }) => (
          <form onSubmit={handleSubmit}>
-            <div>からっぽのフォームです</div>
+            <button type="submit">送信</button>
          </form>
        )}
      />
    );
  };
  export default ReactFinalFormPage;

handleSubmit関数では、仮の処理として、1秒待ってからアラートを表示する、という処理を書いています。

これを呼び出す "送信" ボタン を追加するには、<form>要素の内側に <button type="submit"> 要素を放り込むだけでOKです。

3. Formの中にFieldを設置する

その次に、フィールドを1つ追加してみましょう。

pages/react-final-forms/index.tsx
  const waitMs = (ms: number) =>
    new Promise((resolve) => setTimeout(resolve, ms));

+ const initialValues = {
+  name: "aa",
+ };

+ type ValuesType = typeof initialValues;

  const ReactFinalFormPage: NextPage = () => {
-   const handleSubmit: FormProps["onSubmit"] = async (s) => {
+   type HandleSubmit = FormProps<ValuesType, ValuesType>["onSubmit"];
+   const handleSubmit: HandleSubmit = async (s) => {
      await waitMs(1000);
-     window.alert("送信されました!");
+     window.alert(`JSON: ${JSON.stringify(s)}`);
      // 例) JSON: {"name":"山田太郎"}
    };
    return (
      <Form
        onSubmit={handleSubmit}
+       initialValues={initialValues}
        render={({ handleSubmit }) => (
          <form onSubmit={handleSubmit}>
+           <Field name="name" component="input" />
            <button>送信</button>
          </form>
        )}
      />
    );
  };
  export default ReactFinalFormPage;

"name" という名前を持つフィールドを追加しています。

<Field>コンポーネントに component Prop に "input"と渡すと、 標準の<input>要素を表示してくれます。

<Form>コンポーネントには、 フォームの初期状態のオブジェクトを initialValue Prop に渡します。

送信処理では、フォームの状態を参照して、その内容をJSON形式で出力するようにしています。

(Typescript限定)
initialValuesの型(ValuesType)をFormProps型の型引数に渡さないと、型のエラーになります。
また、それによって、送信処理の引数 s の型が、ValuesType 型に推論されます。

まとめ

いかがだったでしょうか?
React Final Form を使えば、Controlled Component でフォームを作るときのしんどさがかなり緩和されたことが、最小のフォームを作っただけでもわかると思います。
次回以降では、フォームのバリデーションにも触れてみたいと思います。

あとがき:このライブラリの特徴

  • 追加される2つのコンポーネント <Form/>, <Field/> に、 Render Prop を渡す形式
    • それぞれに、 <form/> 要素、 MUI (Material UI) の入力のコンポーネントなどを描画するための レンダー関数を渡すことができる
    • そのレンダー関数の引数を通じて、多様な切り口から見たフォーム・フィールドの状態・フラグにアクセスできる。
      • ( ↓ の緑枠に一部を列挙した)
    • subscribe={{...}} prop に、「レンダー関数の引数のうち、このプロパティだけ監視する」と明示すると、 不要な再描画を防いでくれる
      • subscription-based な設計らしい

フィールドのバリデーションメッセージの表示切り替えに使えそうなフラグの一部

  • フィールドの状態が、初期状態(initialState)に対して
    • dirty: 差分がある時に true
    • pristine: その否定 pristine === !dirty
  • modified: フィールドが一度でも変更されたことがあるなら true
  • visited : フィールドが一度でもフォーカスされたことがあるなら true
  • touched: フィールドが一度でも「フォーカス→フォーカスを外す」をされたことがあるなら true

See also:

Final Form と、そのReactバインディングである React Final Form のドキュメントです

この動画でのライブコーディングも、大いに参考になると思います。

11
4
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
11
4