はじめに
4月から新しい現場で働くようになり、いままで触っていなかったいろんな技術に触れる機会が増えました。
今回はその中のひとつであるFormikというライブラリについて、学習のために自分で調べてわかったことを記事にしました。
簡単なフォームでのFormikの使い方について3つのステップに分けてイチから理解できるようにまとめています。
なお、はじめは環境構築からになるので、単純に使い方を知りたい方は「Formikとは」からご覧ください。
環境構築
Reactアプリの生成
まずはReactの環境を作ります。
npx create-react-app practice-formik-front
practice-formik-front
の部分は各自作成したいフォルダ名に置き換えてください。
※create-react-app
は公式サイトから記載が消えており、非推奨となっているようです。
React単体で使うのではなく、Next.js等のフレームワークと組み合わせて使うことが推奨されています。
今回はサクッと試すだけなのでこちらで環境を作っていきます。
(参考:「フレームワークなしで React を使うことは可能? 」)
ライブラリのインストール
作成したフォルダへ移動し、formikをインストールします。
また、Yupというライブラリも合わせてインストールしておきます。
cd practice-formik-front
npm i formik
npm i yup
ファイルの準備
まず、自動生成されたファイルを削除します。
rm -f src/*
作成したフォルダをVS Codeで開き、src
フォルダの下にindex.js
、index.css
を作成します。
それぞれのファイルは以下の通り記載してください
index.jsの実装
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
const SimpleForm = () => {
return (
<>
<form className="form-container">
<div className="form-group">
<label htmlFor="fullName">full name:</label>
<input
type="text"
id="fullName"
name="fullName"
placeholder="your name"
/>
</div>
<div className="form-group">
<label htmlFor="email">email address:</label>
<input
type="email"
id="email"
name="email"
placeholder="your email-address"
/>
</div>
<button type="submit" className="submit-button">
submit
</button>
</form>
</>
);
};
const App = () => {
return <SimpleForm />;
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
index.cssの実装
.form-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
background-color: #f4f4f4;
border-radius: 4px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input {
width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.submit-button {
display: block;
width: 100%;
padding: 10px;
font-size: 16px;
color: #fff;
background-color: #007bff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.submit-button:hover {
background-color: #0056b3;
}
画面の表示
npm start
を作業中のディレクトリで実行して、アプリを起動します。
以上で環境構築は完了です。
Formikとは
フォームの構築するためのライブラリです。
フォームを作るうえで面倒な以下の3つの作業を楽にしてくれます。
- フォームの状態管理
- バリデーションとエラーハンドリング
- submit処理
それぞれ1つずつ確認していきます。
なお、Formikを使用するにはuserFormik
フックを使用する方法と、Formik
コンポーネントを使用する方法の2種類があります。
今回はuseFormik
フックを使用する方法について確認します。
Formikの使い方 - フォームの状態管理
初期値の定義
Formikに状態管理を任せるには、useFormik
フックにフォームの初期値を定義します。
const initialValues = {
fullName: "",
email: "",
};
/* 中略 */
const { values, handleChange } = useFormik({
initialValues: {
fullName: "",
email: "",
},
});
useFormik
フックの引数にはオブジェクトを定義します。
様々なプロパティがありますがinitialValues
のみ必須で、それ以外は任意です。
initialValues
もオブジェクトで定義します。SimpleFormコンポーネントの外側に定義しておきます。
プロパティにはフォームの各入力項目のname
属性を設定します。
値には初期値を設定します。
また、useFormik
フックは戻り値としてオブジェクトを返します。
オブジェクトには様々な処理が含まれています。
ここでは状態が設定されているvalues
、
フォームの変更に対する処理を行うhandleChange
を取得しています。
values
も実態はオブジェクトで、initialValues
で設定した各項目の状態が含まれています。
フォームへの反映
取得した状態と変更処理をフォームの各項目に設定します。
<input type="text" id="fullName" name="fullName" placeholder="your name"
onChange={handleChange} value={values.fullName} />
/* 中略 */
<input type="text" id="email" name="email" placeholder="your email-address"
onChange={handleChange} value={values.email} />
value
属性にvalues
から取得したそれぞれの値を設定します。
onChange
属性にはhandleChange
をどの項目にも設定します。
その他処理
本筋とは関係ありませんが、フォームの変更を確認しやすいよう処理を追加しておきます。
const printFormik = (e) => {
e.preventDefault();
console.log(`fullName : ${formik.values.fullName}`);
console.log(`email : ${formik.values.email}`);
};
/* 中略 */
<form className="form-container" onSubmit={printFormik}>
printFormik
はvalues
を使用したいのでコンポーネント内に定義します。
submit時に各状態を出力するようにしています。
ここまでのソースコードを反映した全文は以下から確認できます。
index.jsの全文
import React from "react";
import ReactDOM from "react-dom/client";
import { useFormik } from "formik";
import "./index.css";
/* 初期値の定義 */
const initialValues = {
fullName: "",
email: "",
};
const SimpleForm = () => {
/* fomikの定義 */
const { values, handleChange } = useFormik({
/* 初期値 */
initialValues: initialValues,
});
/* submit時の挙動の定義(仮) */
const printFormik = (e) => {
e.preventDefault();
console.log(`fullName : ${values.fullName}`);
console.log(`email : ${values.email}`);
};
return (
<>
{/* onSubmitの追加 */}
<form className="form-container" onSubmit={printFormik}>
<div className="form-group">
<label htmlFor="fullName">full name:</label>
{/* onChangeとvalueの追加 */}
<input
type="text"
id="fullName"
name="fullName"
placeholder="your name"
onChange={handleChange}
value={values.fullName}
/>
</div>
<div className="form-group">
<label htmlFor="email">email address:</label>
{/* onChangeとvalueの追加 */}
<input
type="email"
id="email"
name="email"
placeholder="your email-address"
onChange={handleChange}
value={values.email}
/>
</div>
<button type="submit" className="submit-button">
submit
</button>
</form>
</>
);
};
const App = () => {
return <SimpleForm />;
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
結果の確認
各項目に任意の値を入力し、「submit」ボタンを押下すると、コンソールに各ボタンの情報が出力されます。
このことから、useFormik
フックから取得したvalues
とhandleChange
によって状態の管理が行われていることがわかります。
Formikを使用しない場合useState
フックを使って状態を定義し、各項目に対して変更を処理するためのハンドラを用意する必要があります。
useFormk
によって簡潔に状態を管理することができました。
バリデーションとエラーハンドリング
バリデーション
バリデーションの方法には2種類あります。
- 自分でバリデーション用の関数をカスタマイズして、
useFormik
フックのvalidate
プロパティに渡す - Yupライブラリを使用して、
useFormik
フックの特別なプロパティであるvalidationSchema
に定義する
Yupのほうが簡潔に書けますし、公式サイトにおいてYupライブラリの使用が推奨されているので、こちらを使います。
ライブラリについては冒頭の環境構築にてインストール済です。
バリデーションの定義
SimpleForm
コンポーネントの外に、以下のように定義しておきます。
import * as Yup from "yup";
const validationSchema = Yup.object().shape({
fullName: Yup.string().required().max(15),
email: Yup.string().required().email(),
});
ここではYupの使い方について詳しくは触れませんが、fullName
とemail
それぞれに必須チェックや最大文字数などを定義しています。
エラー時のメッセージは自動で設定されますが、カスタマイズすることも可能です。
useFormikフックの修正
const { values, errors, touched, handleChange, handleBlur } = useFormik({
/* 初期値の定義 */
initialValues: {
fullName: "",
email: "",
},
validationSchema: validationSchema,
});
作成したYupの値をvalidationSchema
に設定します。
また、戻り値としてerror
を受け取ります。これにはフォームの各項目のエラー情報が格納されています。
そのほか、touched
とhandleBlur
も取得しておきます。
この2つはフォームのフォーカスの状態を管理するために使用します。
handleBlur
はフォームからフォーカスが外れたことを検知し、touched
をtrue
にします。
フォームへの反映とエラーハンドリング
<input type="text" id="fullName" name="fullName" placeholder="your name"
onBlur={handleBlur} onChange={handleChange} value={values.fullName} />
{touched.fullName && errors.fullName && <p>{errors.fullName}</p>}
// 中略
<input type="text" id="email" name="email" placeholder="your email-address"
onBlur={handleBlur} onChange={handleChange} value={values.email} />
{touched.email && errors.email && <p>{errors.email}</p>}
onBlur
属性にhandleBlur
を設定しました。
また、エラーメッセージの表示処理も追加しています。
fullName
とemail
それぞれについて、touched
とerrors
から情報を取得し、フォーカスが外れており、かつエラーメッセージがある場合に、メッセージを出力するようにしています。
ここまでのソースコードを反映した全文は以下から確認できます。
index.jsの全文
import React from "react";
import ReactDOM from "react-dom/client";
import { useFormik } from "formik";
import * as Yup from "yup";
import "./index.css";
/* 初期値の定義 */
const initialValues = {
fullName: "",
email: "",
};
/* バリデーションの定義 */
const validationSchema = Yup.object().shape({
fullName: Yup.string().required().max(15),
email: Yup.string().required().email(),
});
const SimpleForm = () => {
/* fomikの定義 */
const { values, errors, touched, handleChange, handleBlur } = useFormik({
/* 初期値 */
initialValues: initialValues,
/* バリデーション */
validationSchema: validationSchema,
});
/* submit時の挙動の定義(仮) */
const printFormik = (e) => {
e.preventDefault();
console.log(`fullName : ${values.fullName}`);
console.log(`email : ${values.email}`);
};
return (
<>
<form className="form-container" onSubmit={printFormik}>
<div className="form-group">
<label htmlFor="fullName">full name:</label>
{/* onBlurの追加 */}
<input
type="text"
id="fullName"
name="fullName"
placeholder="your name"
onBlur={handleBlur}
onChange={handleChange}
value={values.fullName}
/>
{/* エラーメッセージ表示 */}
{touched.fullName && errors.fullName && <p>{errors.fullName}</p>}
</div>
<div className="form-group">
<label htmlFor="email">email address:</label>
{/* onBlurの追加 */}
<input
type="text"
id="email"
name="email"
placeholder="your email-address"
onBlur={handleBlur}
onChange={handleChange}
value={values.email}
/>
{/* エラーメッセージ表示 */}
{touched.email && errors.email && <p>{errors.email}</p>}
</div>
<button type="submit" className="submit-button">
submit
</button>
</form>
</>
);
};
const App = () => {
return <SimpleForm />;
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
結果の確認
必須入力のfullName
にフォーカスを当ててからフォーカスを外すと必須である旨のメッセージが出力されます。
デタラメな値をemailに入力してから、フォーカスを外すと形式不正であるメッセージが出力されます。
Yupを使うことで簡単にバリデーションチェックの処理を定義し、useFormik
フックでフォームの各項目のエラーの状態を管理することができました。
Formikの使い方 - submit処理
現状の処理だと、フォーカスを外したときにはバリデーションが動きますが、「submit」ボタンを押してもバリデーションは動きません。
また、各項目が未入力の状態でもsubmitされます。(ここではログが出力される。)
これらを解決するためにFormik用のsubmit処理を定義します。
submit処理の定義
/* submit処理の定義 */
const customSubmit = (values) => {
console.log(`fullName : ${values.fullName}`);
console.log(`email : ${values.email}`);
};
今まで使っていたprintForm
の処理を削除し、Simpleコンポーネントの外に関数を定義します。
この関数はuseFormik
フックに渡す関数です。
引数としてフォームの値であるvalues
とFormikBag
と呼ばれるFomikのメソッドやプロパティを含んだオブジェクトを受け取ります。
useFormikの修正
const { values, errors, touched, handleChange, handleBlur, handleSubmit } =
useFormik({
/* 初期値 */
initialValues: initialValues,
/* バリデーション */
validationSchema: validationSchema,
/* submit処理 */
onSubmit: customSubmit,
});
onSubmit
プロパティに先ほど定義したsubmit用の関数を設定します。
また、戻り値としてhandleSubmit
を追加します。
onSubmit
プロパティに渡した関数の処理をベースとして、submit時のバリデーションなどを担ってくれる処理です。
フォームへの反映
<form className="form-container" onSubmit={handleSubmit}>
onSubmit
属性に、handleSubmit
を設定します。
これでFormikの基本的な機能の実装は異常です。
ここまでのソースコードを反映した全文は以下から確認できます。
index.jsの全文
import React from "react";
import ReactDOM from "react-dom/client";
import { useFormik } from "formik";
import * as Yup from "yup";
import "./index.css";
/* 初期値の定義 */
const initialValues = {
fullName: "",
email: "",
};
/* バリデーションの定義 */
const validationSchema = Yup.object().shape({
fullName: Yup.string().required().max(15),
email: Yup.string().required().email(),
});
/* submit処理の定義 */
const customSubmit = (values) => {
console.log(`fullName : ${values.fullName}`);
console.log(`email : ${values.email}`);
};
const SimpleForm = () => {
/* fomikの定義 */
const { values, errors, touched, handleChange, handleBlur, handleSubmit } =
useFormik({
/* 初期値 */
initialValues: initialValues,
/* バリデーション */
validationSchema: validationSchema,
/* submit処理 */
onSubmit: customSubmit,
});
return (
<>
{/* onSubmitの修正 */}
<form className="form-container" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="fullName">full name:</label>
<input
type="text"
id="fullName"
name="fullName"
placeholder="your name"
onBlur={handleBlur}
onChange={handleChange}
value={values.fullName}
/>
{touched.fullName && errors.fullName && <p>{errors.fullName}</p>}
</div>
<div className="form-group">
<label htmlFor="email">email address:</label>
<input
type="text"
id="email"
name="email"
placeholder="your email-address"
onBlur={handleBlur}
onChange={handleChange}
value={values.email}
/>
{touched.email && errors.email && <p>{errors.email}</p>}
</div>
<button type="submit" className="submit-button">
submit
</button>
</form>
</>
);
};
const App = () => {
return <SimpleForm />;
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
結果の確認
未入力の状態で「submit」ボタンを押すと、バリデーションチェックが行われ、エラーメッセージが表示されます。
また、エラーでsubmit処理が行われないので、コンソールには何も表示されていません。
任意の値を入力して「submit」ボタンを押すと、エラーメッセージは表示されません。
また、submit処理が適切に行われたため、コンソールに値が出力されています。
まとめ
フォームを簡単に実装することのできるFormikの使い方を確認してきました。
フォームの状態管理、バリデーションとエラーハンドリング、submit時の処理
どれをとっても、自分で実装するよりも簡単に作ることができました。
他にもまだまだ便利なコンポーネントやメソッドがあるようなので、実務を通して学んでいきたいと思いました。
また、学んだ結果を別の機会にまとめようとも思っています。
参考にしたサイト
おまけ
今回の記事を作成するにあたって、自分でわからなくて調べたり、うまくいかなくて詰まったりしたところをいくつか紹介します。
疑問1 : 同じhandleChangeでちゃんと管理できてるの?
→name属性で判断して別々に管理しているので問題ない
それぞれの入力に対して、同じhandleChange
だったり、handleBlur
だったりを設定しています。
それでも、各項目に対しての状態管理が正しく行われているようです。これはどういうことなのかよくわからなかったのですが、正解はname
属性で見分けているようです。
わかりやすく理解するためにFormikを使わずに、useState
フックで書くと次のようになるとのことでした。
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
});
}
※公式チュートリアルより抜粋
疑問2 : カスタムsubmit処理でpreventDefaultを呼んでいないのに、submit処理がブロックされているように見える
→内部で自動的にpreventDefaultを呼んでいる
もともと定義していたprintFormik
の中ではpreventDefault
を呼んでいましたが、formikのsubmit処理を利用するために設定したcustomSubmit
の中では呼んでいません。
しかし、デフォルトのsubmit処理は実行されず、カスタマイズした処理が実行されています。
これは内部で勝手に呼ばれているからだそうです。
submit時にバリデーションチェクが入るのと同じ感じで、勝手にFormik側でやっているようです。