この記事を読むと幸せになれそうな人
React.useState
や Redux を使う方法で大きめのフォームを構築するとき、UIライブラリを使ったり、バリデーションを実装したりすると、ボイラープレートコードが多すぎて辟易してしまうと思います。
そんな人には、この React Final Form がオススメかもしれません。
(個人的には React Hooks Form よりもこっちの方が明示的で好きです...)
今回やること
- #01『かんたんにフォームを構築する』←今ここ
- #02『Render Propsを使って、かんたんにバリデーションを実装する』
- #03『かんたんに2回クリックの防止とサーバー側バリデーションを実装する』
今回はとりあえず、フォームを構築して、送信処理を実装するところまでを実装します。
1. Form
コンポーネントを設置する。
当記事では、テスト用のページを手早く用意するために Next.js を、使い慣れているので Typescript を使っていますが、内容には特に関係ありません。
テストする場合はそれぞれのやりやすい環境でどうぞ。
まずは、中身も空っぽ、送信時に何もしないフォームを作成してみましょう。
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.送信ボタンを追加する
それでは、とにかくダミーの送信処理と、送信ボタンを追加してみましょう。
+ 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つ追加してみましょう。
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
-
dirty: 差分がある時に
-
modified: フィールドが一度でも変更されたことがあるなら
true
-
visited : フィールドが一度でもフォーカスされたことがあるなら
true
-
touched: フィールドが一度でも「フォーカス→フォーカスを外す」をされたことがあるなら
true
See also:
Final Form と、そのReactバインディングである React Final Form のドキュメントです
この動画でのライブコーディングも、大いに参考になると思います。