チュートリアル

始める前に

Formikのチュートリアルへようこそ。このチュートリアルでは、Reactでシンプルで複雑なフォームを作成するために必要なすべてを学ぶことができます。

すぐにでもローカルマシンで作業を始めたい場合は、60秒クイックスタートをご覧ください。

何を作成するのか?

このチュートリアルでは、ReactとFormikを使用して複雑なニュースレター登録フォームを作成します。

作成する内容はこちらで確認できます:最終結果。コードが理解できない場合でも心配しないでください!このチュートリアルの目標は、Formikを理解するのに役立つことです。

前提条件

Formikとその仕組みを完全に理解するには、HTML、CSS、モダンJavaScript、およびReact(およびReact Hooks)に関する知識が必要です。このチュートリアルでは、アロー関数letconstスプレッド構文デストラクチャリング計算されたプロパティ名、およびasync/awaitを使用しています。Babel REPLを使用して、ES6コードがどのようにコンパイルされるかを確認できます。

チュートリアルの設定

このチュートリアルを完了するには、2つの方法があります。ブラウザでコードを作成するか、コンピュータにローカル開発環境を設定します。

設定オプション1:ブラウザでコードを作成する

これは最も迅速な開始方法です!

まず、このスターターコードを新しいタブで開きます。新しいタブには、メールアドレス入力、送信ボタン、いくつかのReactコードが表示されます。このチュートリアルでは、Reactコードを編集します。

2番目の設定オプションをスキップして、概要セクションに進み、Formikの概要を確認してください。

設定オプション2:ローカル開発環境

これは完全にオプションであり、このチュートリアルには必要ありません!

オプション:お好みのテキストエディタを使用してローカルで作業するための手順

この設定にはより多くの作業が必要ですが、選択したエディタを使用してチュートリアルを完了できます。手順は以下のとおりです。

  1. 最新バージョンのNode.jsがインストールされていることを確認してください。
  2. Create React Appのインストール手順に従って、新しいプロジェクトを作成します。
npx create-react-app my-app
  1. Formikをインストール
npm i formik

または

yarn add formik
  1. 新しいプロジェクトの `src/` フォルダー内のすべてのファイルを削除します

注意

`src` フォルダー全体を削除しないでください。内部にある元のソースファイルのみを削除してください。次の手順では、このプロジェクトの例を使用してデフォルトのソースファイルを置き換えます。

cd my-app
cd src
# If you’re using a Mac or Linux:
rm -f *
# Or, if you’re on Windows:
del *
# Then, switch back to the project folder
cd ..
  1. `src/` フォルダーに `styles.css` という名前のファイルを追加し、このCSSコードを追加します。

  2. `src/` フォルダーに `index.js` という名前のファイルを追加し、このJSコードを追加します。

次に、プロジェクトフォルダーで `npm start` を実行し、ブラウザで `http://localhost:3000` を開きます。メール入力と送信ボタンが表示されます。

エディタの構文ハイライトを設定するには、これらの手順に従うことをお勧めします。

行き詰まりました!

行き詰まった場合は、FormikのGitHubディスカッションを確認してください。さらに、FormiumコミュニティDiscordサーバーは、迅速なヘルプを得るための優れた方法でもあります。回答を受け取れない場合、または行き詰まったままである場合は、問題を報告してください。お手伝いいたします。

概要:Formikとは?

Formikは、ReactとReact Nativeでフォームを作成するための、少数のReactコンポーネントとフックのグループです。最も厄介な3つの部分を支援します。

  1. フォームの状態への値の入出力
  2. バリデーションとエラーメッセージ
  3. フォーム送信の処理

Formikは上記をすべて一箇所に配置することで、整理整頓を維持し、フォームのテスト、リファクタリング、および推論を簡単に行うことができます。

基本

Formikを使用する最も冗長な方法から始めます。これは少し回りくどいように見えるかもしれませんが、Formikがどのように構築されているかを理解し、それがどのように機能するかの完全なメンタルモデルを持つことが重要です。

シンプルなニュースレター登録フォーム

ブログのニュースレター登録フォームを追加したいとしましょう。まず、フォームには `email` という名前のフィールドが1つだけあります。Formikを使用すると、これはわずか数行のコードです。

import React from 'react';
import { useFormik } from 'formik';
const SignupForm = () => {
// Pass the useFormik() hook initial form values and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
email: '',
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};

フォームの `initialValues` と送信関数 (`onSubmit`) を `useFormik()` フックに渡します。フックは、`formik` と呼ぶ変数に、フォームの状態とヘルパーメソッドの詰め合わせを返します。今のところ、重要なヘルパーメソッドは次のとおりです。

  • `handleSubmit`:送信ハンドラー
  • `handleChange`:各 `<input>`、`<select>`、または `<textarea>` に渡す変更ハンドラー
  • `values`:フォームの現在の値

上記のように、それぞれをそれぞれのpropsに渡します…それだけ!これで、Formikを搭載した機能するフォームを作成できます。フォームの値を自分で管理し、各入力ごとに独自のイベントハンドラーを作成する代わりに、`useFormik()` を使用できます。

これはかなり素晴らしいですが、入力は1つだけで、`useFormik()` を使用する利点は不明です。そこで、ユーザーの名前と姓を入力するためのさらに2つの入力(フォームでは `firstName` と `lastName` として保存)を追加しましょう。

import React from 'react';
import { useFormik } from 'formik';
const SignupForm = () => {
// Note that we have to initialize ALL of fields with values. These
// could come from props, but since we don’t want to prefill this form,
// we just use an empty string. If we don’t do this, React will yell
// at us.
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
value={formik.values.firstName}
/>
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};

新しいコードをよく見ると、いくつかのパターンと対称性が生まれていることに気付くでしょう。

  1. 同じ変更ハンドラー関数 `handleChange` を各HTML入力で再利用します。
  2. `initialValues` で定義したプロパティと一致する `id` と `name` のHTML属性を渡します。
  3. 同じ名前を使用してフィールドの値にアクセスします(`email` -> `formik.values.email`)

プレーンなReactを使用してフォームを作成することに慣れている場合は、Formikの `handleChange` は次のように機能すると考えることができます。

const [values, setValues] = React.useState({});
const handleChange = event => {
setValues(prevValues => ({
...prevValues,
// we use the name to tell Formik which key of `values` to update
[event.target.name]: event.target.value
});
}

バリデーション

連絡先フォームは機能しますが、機能は完全ではありません。ユーザーは送信できますが、どのフィールド(もしあれば)が必要なのかは通知されません。

ブラウザの組み込みHTML入力バリデーションを使用しても構わない場合は、各入力に `required` プロパティを追加し、最小/最大長(`maxlength` と `minlength`)、またはこれらの各入力の正規表現バリデーションのための `pattern` プロパティを指定できます。これらは、それらを使用できる場合は素晴らしいです。ただし、HTMLバリデーションには限界があります。まず、ブラウザでのみ機能します!そのため、React Nativeでは明らかに実行できません。第二に、ユーザーにカスタムエラーメッセージを表示することは困難または不可能です。第三に、非常にぎこちないものです。

前述のように、Formikはフォームの `values` だけでなく、そのバリデーションとエラーメッセージも追跡します。JSでバリデーションを追加するには、カスタムバリデーション関数を指定し、`useFormik()` フックに `validate` として渡します。エラーが存在する場合は、このカスタムバリデーション関数は、`values` / `initialValues` と一致する形状の `error` オブジェクトを生成する必要があります。繰り返しますが… _対称性_ …はい…

import React from 'react';
import { useFormik } from 'formik';
// A custom validation function. This must return an object
// which keys are symmetrical to our values/initialValues
const validate = values => {
const errors = {};
if (!values.firstName) {
errors.firstName = 'Required';
} else if (values.firstName.length > 15) {
errors.firstName = 'Must be 15 characters or less';
}
if (!values.lastName) {
errors.lastName = 'Required';
} else if (values.lastName.length > 20) {
errors.lastName = 'Must be 20 characters or less';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
return errors;
};
const SignupForm = () => {
// Pass the useFormik() hook initial form values, a validate function that will be called when
// form values change or fields are blurred, and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validate,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
value={formik.values.firstName}
/>
{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null}
<button type="submit">Submit</button>
</form>
);
};

`formik.errors` はカスタムバリデーション関数によって設定されます。デフォルトでは、Formikはキーストローク(変更イベント)ごと、各入力のblurイベントごと、および送信前にバリデーションを実行します。`useFormik()` に渡した `onSubmit` 関数は、エラーがない場合(つまり、`validate` 関数が `{}` を返す場合)のみ実行されます。

訪問済みフィールド

フォームは機能しており、ユーザーは各エラーを確認できますが、ユーザーエクスペリエンスとしては最適ではありません。バリデーション関数はすべてのフォームのvaluesに対して各キーストロークで実行されるため、errorsオブジェクトには、任意の時点ですべてのバリデーションエラーが含まれます。コンポーネントでは、エラーが存在するかどうかをチェックし、すぐにユーザーに表示しています。ユーザーがまだアクセスしていないフィールドのエラーメッセージも表示されるため、これは不適切です。ほとんどの場合、ユーザーがそのフィールドへの入力を終えたに、フィールドのエラーメッセージを表示するだけで十分です。

errorsvaluesと同様に、Formikはどのフィールドがアクセスされたか追跡します。この情報は、values/initialValuesの形状を反映するtouchedというオブジェクトに格納されます。touchedのキーはフィールド名、値はブール値true/falseです。

touchedを活用するために、各入力のonBlurプロパティにformik.handleBlurを渡します。この関数は、name属性を使用して更新するフィールドを特定するという点で、formik.handleChangeと同様に機能します。

import React from 'react';
import { useFormik } from 'formik';
const validate = values => {
const errors = {};
if (!values.firstName) {
errors.firstName = 'Required';
} else if (values.firstName.length > 15) {
errors.firstName = 'Must be 15 characters or less';
}
if (!values.lastName) {
errors.lastName = 'Required';
} else if (values.lastName.length > 20) {
errors.lastName = 'Must be 20 characters or less';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
return errors;
};
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validate,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null}
<button type="submit">Submit</button>
</form>
);
};

もうすぐです!touchedを追跡できるようになったので、エラーメッセージのレンダリングロジックを変更して、特定のフィールドのエラーメッセージを、それが存在しかつユーザーがそのフィールドにアクセスした場合にのみ表示するようにできます。

import React from 'react';
import { useFormik } from 'formik';
const validate = values => {
const errors = {};
if (!values.firstName) {
errors.firstName = 'Required';
} else if (values.firstName.length > 15) {
errors.firstName = 'Must be 15 characters or less';
}
if (!values.lastName) {
errors.lastName = 'Required';
} else if (values.lastName.length > 20) {
errors.lastName = 'Must be 20 characters or less';
}
if (!values.email) {
errors.email = 'Required';
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid email address';
}
return errors;
};
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validate,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};

Yupを使ったスキーマバリデーション

上記のように、バリデーションはユーザー自身に任されています。独自のバリデーターを作成するか、サードパーティのヘルパーライブラリを使用できます。Formikの開発者/多くのユーザーは、Jason QuenseのライブラリYupをオブジェクトスキーマのバリデーションに使用しています。YupはJoiReact PropTypesと同様のAPIを持っていますが、ブラウザでも動作し、ランタイムでも十分な速度です。このREPLで試すことができます。

Formikの開発者/ユーザーがYupを非常に気に入っているため、FormikにはYup用の特別な設定プロパティvalidationSchemaがあります。これは、Yupのバリデーションエラーメッセージを、values/initialValues/touchedとキーが一致する(カスタムバリデーション関数と同じように)美しいオブジェクトに自動的に変換します。いずれにしても、NPM/yarnからYupを次のようにインストールできます…

npm install yup --save
# or via yarn
yarn add yup

Yupの動作を確認するために、カスタムバリデーション関数validateを削除し、YupとvalidationSchemaを使用してバリデーションを書き直してみましょう。

import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
}),
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};

繰り返しますが、Yupは完全にオプションです。しかし、試してみることをお勧めします。上記のように、30行のコードではなく、わずか10行のコードでまったく同じバリデーション関数を表現できました。Formikの中核となる設計原則の1つは、整理整頓を支援することです。Yupは間違いなくこの点で大きく役立ちます。スキーマは非常に表現力豊かで直感的(値を反映するため)であり、再利用可能です。Yupを使用するかどうかとは関係なく、アプリケーション全体で一般的に使用されるバリデーションメソッドを共有することを強くお勧めします。これにより、一般的なフィールド(例:メールアドレス、住所、ユーザー名、電話番号など)の一貫したバリデーションが保証され、より良いユーザーエクスペリエンスにつながります。

ボイラープレートの削減

getFieldProps()

上記のコードは、Formikが正確に何をしているかを非常に明確に示しています。onChange -> handleChangeonBlur -> handleBlurなどです。しかし、時間を節約するために、useFormik()はヘルパーメソッドformik.getFieldProps()を返し、入力の接続を高速化します。フィールドレベルの情報が与えられると、特定のフィールドのonChangeonBlurvaluecheckedの正確なグループが返されます。それをinputselect、またはtextareaに展開できます。

import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
}),
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
type="text"
{...formik.getFieldProps('firstName')}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input id="lastName" type="text" {...formik.getFieldProps('lastName')} />
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input id="email" type="email" {...formik.getFieldProps('email')} />
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};

Reactコンテキストの活用

上記のコードは、Formikが正確に何をしているかを非常に明確に示しています。onChange -> handleChangeonBlur -> handleBlurなどです。しかし、各入力にこの「プロップゲッター」getFieldProps()を手動で渡す必要があります。さらに時間を節約するために、FormikにはReactコンテキストベースのAPI/コンポーネントが付属しており、より簡単にコードを簡潔にすることができます:<Formik /><Form /><Field /><ErrorMessage />。より具体的には、Reactコンテキストを暗黙的に使用して、親<Formik />の状態/メソッドに接続します。

これらのコンポーネントはReactコンテキストを使用するため、フォームの状態とヘルパーをツリーに保持するReactコンテキストプロバイダーをレンダリングする必要があります。自分でこれを行うと、次のようになります。

import React from 'react';
import { useFormik } from 'formik';
// Create empty context
const FormikContext = React.createContext({});
// Place all of what’s returned by useFormik into context
export const Formik = ({ children, ...props }) => {
const formikStateAndHelpers = useFormik(props);
return (
<FormikContext.Provider value={formikStateAndHelpers}>
{typeof children === 'function'
? children(formikStateAndHelpers)
: children}
</FormikContext.Provider>
);
};

幸いにも、<Formik>コンポーネントでこれを行いました。これはまさにこれと同じように機能します。

これで、useFormik()フックをFormikの<Formik>コンポーネント/レンダープロップに置き換えます。コンポーネントなので、useFormik()に渡されたオブジェクトをJSXに変換し、各キーをプロップにします。

import React from 'react';
import { Formik } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
return (
<Formik
initialValues={{ firstName: '', lastName: '', email: '' }}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
type="text"
{...formik.getFieldProps('firstName')}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
type="text"
{...formik.getFieldProps('lastName')}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input id="email" type="email" {...formik.getFieldProps('email')} />
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
)}
</Formik>
);
};

上記のように、useFormik()フックを<Formik>コンポーネントに置き換えました。<Formik>コンポーネントは、子として関数を受け入れます(別名レンダープロップ)。その引数は、useFormik()によって返されるオブジェクトとまったく同じです(実際、<Formik>は内部的にuseFormik()を呼び出します!)。したがって、フォームは以前と同じように機能しますが、新しいコンポーネントを使用して、より簡潔な方法で表現できるようになりました。

import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
return (
<Formik
initialValues={{ firstName: '', lastName: '', email: '' }}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" />
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" />
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
};

<Field>コンポーネントは、デフォルトで<input>コンポーネントをレンダリングします。これは、nameプロップが与えられると、それぞれのonChangeonBlurvalueプロップを暗黙的に取得して要素に渡すと同時に、渡されたプロップも渡します。しかし、すべてが入力ではないため、<Field>は、自由にレンダリングできるようにいくつかのプロップも受け入れます。いくつかの例…

// <input className="form-input" placeHolder="Jane" />
<Field name="firstName" className="form-input" placeholder="Jane" />
// <textarea className="form-textarea"/></textarea>
<Field name="message" as="textarea" className="form-textarea" />
// <select className="my-select"/>
<Field name="colors" as="select" className="my-select">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</Field>

Reactはコンポジションがすべてであり、プロップドリリングを多く削減しましたが、各入力にlabel<Field><ErrorMessage>を繰り返しています。抽象化でより良いことができます!Formikを使用すると、アプリケーション全体で共有できる再利用可能な入力プリミティブコンポーネントを作成できます。<Field>レンダープロップコンポーネントには姉妹がいて、その名前はuseFieldで、同じことを行いますが、Reactフックを使用します!ご覧ください…

import React from 'react';
import ReactDOM from 'react-dom';
import { Formik, Form, useField } from 'formik';
import * as Yup from 'yup';
const MyTextInput = ({ label, ...props }) => {
// useField() returns [formik.getFieldProps(), formik.getFieldMeta()]
// which we can spread on <input>. We can use field meta to show an error
// message if the field is invalid and it has been touched (i.e. visited)
const [field, meta] = useField(props);
return (
<>
<label htmlFor={props.id || props.name}>{label}</label>
<input className="text-input" {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
const MyCheckbox = ({ children, ...props }) => {
// React treats radios and checkbox inputs differently from other input types: select and textarea.
// Formik does this too! When you specify `type` to useField(), it will
// return the correct bag of props for you -- a `checked` prop will be included
// in `field` alongside `name`, `value`, `onChange`, and `onBlur`
const [field, meta] = useField({ ...props, type: 'checkbox' });
return (
<div>
<label className="checkbox-input">
<input type="checkbox" {...field} {...props} />
{children}
</label>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
const MySelect = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<select {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
// And now we can use these
const SignupForm = () => {
return (
<>
<h1>Subscribe!</h1>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
acceptedTerms: false, // added for our checkbox
jobType: '', // added for our select
}}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string()
.email('Invalid email address')
.required('Required'),
acceptedTerms: Yup.boolean()
.required('Required')
.oneOf([true], 'You must accept the terms and conditions.'),
jobType: Yup.string()
.oneOf(
['designer', 'development', 'product', 'other'],
'Invalid Job Type'
)
.required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<MyTextInput
label="First Name"
name="firstName"
type="text"
placeholder="Jane"
/>
<MyTextInput
label="Last Name"
name="lastName"
type="text"
placeholder="Doe"
/>
<MyTextInput
label="Email Address"
name="email"
type="email"
placeholder="jane@formik.com"
/>
<MySelect label="Job Type" name="jobType">
<option value="">Select a job type</option>
<option value="designer">Designer</option>
<option value="development">Developer</option>
<option value="product">Product Manager</option>
<option value="other">Other</option>
</MySelect>
<MyCheckbox name="acceptedTerms">
I accept the terms and conditions
</MyCheckbox>
<button type="submit">Submit</button>
</Form>
</Formik>
</>
);
};

上記のように、useField()を使用すると、あらゆる種類のReactコンポーネントを入力として<Field> + <ErrorMessage>のようにFormikに接続できます。これを使用して、ニーズに合った再利用可能な入力グループを作成できます。

まとめ

おめでとうございます!Formikを使用してサインアップフォームを作成しました。

  • 複雑なバリデーションロジックと詳細なエラーメッセージがあります。
  • エラーメッセージを適切なタイミング(フィールドをぼかした後)でユーザーに正しく表示します。
  • アプリの他のフォームで使用できる独自の入力コンポーネントを活用します。

素晴らしい仕事です!Formikの仕組みをある程度理解できたことを願っています。

最終的な結果はこちらをご覧ください:最終結果

時間がある場合、または新しいFormikスキルを練習したい場合は、難易度順にサインアップフォームを改善するためのアイデアをいくつかご紹介します。

  • ユーザーが送信を試行している間、送信ボタンを無効にします(ヒント:formik.isSubmitting)。
  • formik.handleResetまたは<button type="reset">を使用して、リセットボタンを追加します。
  • URLクエリ文字列または<SignupForm>に渡されたプロップに基づいてinitialValuesを事前に設定します。
  • フィールドにエラーがあり、フォーカスされていない場合、入力の枠線の色を赤に変更します。
  • エラーが表示され、アクセスされている各フィールドにシェイクアニメーションを追加します。
  • フォームの状態をブラウザのsessionStorageに永続化し、ページのリフレッシュ間でフォームの進捗状況を維持します。

このチュートリアルでは、フォームの状態、フィールド、バリデーション、フック、レンダープロップ、Reactコンテキストなど、Formikの概念について説明しました。これらのトピックの詳細については、その他のドキュメントを参照してください。チュートリアルでコンポーネントとフックを定義する方法の詳細については、APIリファレンスを参照してください。

このページは役に立ちましたか?

ニュースレターの購読

最新のFormikニュース、記事、リソースをメールで受信します。

Copyright © 2020 Formium, Inc. All rights reserved.