基本の使い方
それでは、実際の使い方を見ていきましょう。簡単な例として、Webサービスを使い始めるときの申し込みフォームを作ってみます(図2)。
メールアドレスを入力して送信するだけのシンプルなものです(リスト3)。
import React from 'react';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import './App.css';
// 通信のダミー処理。2秒待つと成功する。
const postData = () => {
return new Promise(resolve => {
setTimeout(resolve, 2000);
});
};
const styles = {
form: {
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
},
};
function App() {
return (
<div className="App">
<h2>基本の使い方 - 申し込みフォーム</h2>
<Formik // (1)
initialValues={{ // (2)
email: '',
}}
validate={values => { // (3)
const errors = {};
if (!values.email) {
errors.email = 'Eメールを入力してください' // (7)
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => { // (4)
postData(values)
.then(() => {
alert('送信完了しました\n' + JSON.stringify(values, null, 2));
setSubmitting(false); // (6)
});
}}
>
{formik => ( // (9)
<Form style={styles.form}>{/* (5) */}
<Field type="email" name="email" />{/* (6) */}
<ErrorMessage name="email" component="div" />
<button type="submit" disabled={formik.isSubmitting}>送信</button>{/* (8) */}
</Form>
)}
</Formik>
</div>
);
}
export default App;
最も外側に配置される、(1)の<Formik>要素は、状態管理の責務を一手に担います。入力フォームの初期状態は(2)のinitialValues属性にオブジェクトとして登録し、バリデーション処理は(3)のvalidate属性に関数を登録して実施します。送信ボタンを押した際の送信処理は(4)のonSubmit属性に記述しています。初期値登録→内容チェック→送信、という、入力フォームに求められるデータ処理が<Formik>要素に集約されていることがわかります。
(5)の<Form>要素は、HTMLの<form>要素の薄いラッパーです。(6)で<Field>要素に記述したname属性は、<Formik>要素が管理するデータとのひもづけに使われます。(2)で設定した初期値のキー名とひもづいたり、(7)でエラーメッセージを登録する際のキー名にも使われます。
バリデーション処理の関数(3)は、{ [Fieldのname]: 'エラーメッセージ' }という形式のエラーオブジェクトを返却することで、UI側の対応する `<Field>` 要素に対してエラーメッセージの表示を促します。リスト3の入力フォームでは、入力欄を空にしたまま送信ボタンを押すと、図3のようにエラーメッセージを表示します。
バリデーション処理では、引数のvaluesですべての入力項目を得られるので、複数の入力欄の組み合わせでバリデーションを行うことも可能です。
前述の通り、<Formik>要素と<Field>要素を組み合わせて使う場合は、<Formik>要素で管理されているデータが自動的に<Field>要素にひも付きけられます。しかし、(8)のように通常のHTML要素と組み合わせたい場合にはこの機構が働きません。こういった場合は、(9)のように無名関数をコンポーネントの代わりに配置することで、<Formik>要素が管理しているデータを直接受け取ることもできます。
任意のコンポーネントと組み合わせる
Formikが提供するUIコンポーネントを利用することで、簡単にフォームを作成できることがわかりましたが、アプリケーション開発の現場では、まっさらなHTMLの<form>要素や<input>要素を加工するなど、よそから持ってきたUIコンポーネントを扱えたほうが都合がいい場合も多いでしょう。Formikは、状態管理ライブラリとしての側面のみを利用した場合にもとても便利です。
Formikは状態管理のインターフェースとして、前述の <Formik>要素を使う方法と、React HooksのカスタムフックであるuseFormikを使う方法の2種類を提供しています(リスト4)。React Hooksについてはこちらの記事が詳しいので、よろしければご覧ください。
// <Formik>要素のchildrenを使う方法
const ChildrenPropsPattern = () => (
<Formik /* 各種状態管理の設定 */>
{formik => { // (1)
return (
/* 任意のコンポーネントでformikオブジェクトを利用する */
)
}}
</Formik>
);
// component属性を使ってもよい
const ComponentPropsPattern = () => (
<Formik
/* 各種状態管理の設定 */
component={formik => { // (1)
return (
/* 任意のコンポーネントでformikオブジェクトを利用する */
)
}}
/>
);
// useFormikを使う方法
const HooksPattern = () => {
// (2)
const formik = useFormik({ /* 各種状態管理の設定 */ });
return (
/* 任意のコンポーネントでformikオブジェクトを利用する */
);
};
見た目は随分と違いますが、(1)と(2)で提供されるformikオブジェクトは同一のものなので、機能面でできることに違いはありません。好みや設計によって使い分けることができます。
リスト5では、リスト3の申し込みフォームを useFormikとHTMLの<form>要素・ <input>要素で作り直してみました。
const SignupFormWithHooks = () => {
const formik = useFormik({
initialValues: {
email: '',
},
validate: values => {
const errors = {};
if (!values.email) {
errors.email = 'Eメールを入力してください'
}
return errors;
},
onSubmit: (values, { setSubmitting }) => {
postData(values)
.then(() => {
alert('送信完了しました\n' + JSON.stringify(values, null, 2));
setSubmitting(false);
});
},
});
return (
<div>
<h2>基本の使い方 - 申し込みフォーム(React Hooks版)</h2>
<form
onSubmit={formik.handleSubmit} // (2)
style={styles.form}
>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange} // (2)
onBlur={formik.handleBlur} // (2)
value={formik.values.email} // (1)
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null /* (3) */}
<button type="submit" disabled={formik.isSubmitting}>送信</button>
</form>
</div>
)
}
initialValuesやvalidateの設定は全く変えていないので、実質的な変更箇所はJSXの部分のみとなります。FormikのUIコンポーネントを使わなくなったことで、(1)で表示データを適用している部分や、(2)でコールバック関数を登録している部分、(3)でエラーメッセージを表示している部分に変更がありました。リスト3に比べると生々しい書き方にはなりましたが、逆にいえば何をやっているのか見えやすくなりましたね。
このように、FormikのUIコンポーネントを使わなくても、Formikの状態管理の恩恵を受けることができます。HTML要素だけではなく、他のUIライブラリが提供するコンポーネントと組み合わせながらフォームを組み立てていくとよいでしょう。
Formikのインターフェース
<Formik>やuseFormikから受け取れるオブジェクトには、各入力欄の情報やバリデーション結果をはじめとした各種データと、それらに影響を与えるための関数が格納されています。利用できるプロパティの一覧を表1にまとめました。
| 名前 | データ型(TypeScript準拠) | 概要 |
|---|---|---|
| dirty |
boolean |
各入力欄がinitialValuesのままならfalse、変更があればtrue |
| errors |
{ [field: string]: string } |
バリデーションの結果( validateの戻り値) |
| handleBlur |
(e: any) => void |
onBlurイベントを受け取る |
| handleChange |
(e: React.ChangeEvent<any>) => void |
onChangeイベントを受け取ってvaluesを更新する |
| handleReset |
() => void |
onResetイベントを受け取って入力フォームを初期状態に戻す |
| handleSubmit |
(e: React.FormEvent<HTMLFormElement>) => void |
onSubmitイベントを受け取って、送信処理を開始する |
| isSubmitting |
boolean |
送信中を表し、handleSubmitでtrueになった後、 setSubmitting(false) を実行するまでtrueのままになる |
| isValid |
boolean |
errorsが空であり、すべての入力欄にエラーがない |
| isValidating |
boolean |
バリデーション処理中 |
| resetForm |
(nextState?: Partial<FormikState<Values>>) => void |
入力フォームをリセットする(引数でリセット後の内部状態を指定できる) |
| setErrors |
(fields: { [field: string]: string }) => void |
errorsを上書きする |
| setFieldError |
(field: string, errorMsg: string) => void |
errors内の特定の入力欄についてのエラーを上書きする |
| setFieldTouched |
(field: string, isTouched?: boolean, shouldValidate?: boolean) => void |
特定の入力欄のタッチ状態を強制的に上書きする |
| submitForm |
() => Promise |
送信処理を開始する(バリデーションに失敗するとPromiseが失敗を返す) |
| submitCount |
number |
handleSubmitが何回呼ばれたか(handleReset でゼロに戻る) |
| setFieldValue |
(field: string, value: any, shouldValidate?: boolean) => void |
values内の特定の入力欄についての値を上書きする |
| setSubmitting |
setSubmitting: (isSubmitting: boolean) => void |
isSubmittingを上書きする |
| setTouched |
(fields: { [field: string]: boolean }, shouldValidate?: boolean) => void |
指定した入力欄のtouchedを更新する |
| setValues |
(fields: React.SetStateAction<{ [field: string]: any }>, shouldValidate?: boolean) => void |
複数の入力欄の値を一度に更新する |
| status |
any |
自由な値を入れてよい |
| touched |
{ [field: string]: boolean } |
現在どのフィールドがタッチ状態になっているか |
| values |
{ [field: string]: any } |
各入力欄の入力値 |
| validateForm |
(values?: any) => Promise<FormikErrors<Values>> |
バリデーション処理を実行する |
| validateField |
(field: string) => void |
特定の入力欄のみにバリデーション処理を実行する |
これらの値や操作を組み合わせることで、多くの入力欄を持つ入力フォームでも、比較的安全に状態管理を行うことができるでしょう。
まとめ
今回は入力フォームの管理を行うFormikについて解説しました。入力項目の数が増えても複雑さを最小限に抑えることができるので、ぜひ使ってみましょう。次回はFormikとも相性がいいバリデーション用のライブラリとして、Yupについて解説します。
