Reactでフォームを作るために、Formikをイチから理解する

## はじめに
4月から新しい現場で働くようになり、いままで触っていなかったいろんな技術に触れる機会が増えました。

今回はその中のひとつであるFormikというライブラリについて、学習のために自分で調べてわかったことを記事にしました。
簡単なフォームでのFormikの使い方について3つのステップに分けてイチから理解できるようにまとめています。

なお、はじめは環境構築からになるので、単純に使い方を知りたい方は「Formikとは」からご覧ください。

## 環境構築
### Reactアプリの生成
まずはReactの環境を作ります。
```bash
npx create-react-app practice-formik-front
```
`practice-formik-front`の部分は各自作成したいフォルダ名に置き換えてください。

※`create-react-app`は公式サイトから記載が消えており、非推奨となっているようです。
React単体で使うのではなく、Next.js等のフレームワークと組み合わせて使うことが推奨されています。
今回はサクッと試すだけなのでこちらで環境を作っていきます。
（参考：「[フレームワークなしで React を使うことは可能？ ](https://ja.react.dev/learn/start-a-new-react-project#can-i-use-react-without-a-framework)」）

### ライブラリのインストール
作成したフォルダへ移動し、formikをインストールします。
また、Yupというライブラリも合わせてインストールしておきます。　
```bash
cd practice-formik-front
npm i formik
npm i yup
```
### ファイルの準備
まず、自動生成されたファイルを削除します。
```bash
rm -f src/*
```
作成したフォルダをVS Codeで開き、`src`フォルダの下に`index.js`、`index.css`を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/6db6661f-bfa2-2e05-b41c-507c7c6e4da2.png)

それぞれのファイルは以下の通り記載してください
<details><summary>index.jsの実装</summary>

```react: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 />);
```

</details>

<details><summary>index.cssの実装</summary>

```css: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;
}
```

</details>

### 画面の表示
```bash
npm start
```
を作業中のディレクトリで実行して、アプリを起動します。

以下のような画面が表示されます。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/b5b79350-36dc-ce1e-9f1a-b0d8dda6c19b.png'>

以上で環境構築は完了です。

## Formikとは
フォームの構築するためのライブラリです。
フォームを作るうえで面倒な以下の3つの作業を楽にしてくれます。
1. フォームの状態管理
1. バリデーションとエラーハンドリング
1. submit処理

それぞれ1つずつ確認していきます。
なお、Formikを使用するには`userFormik`フックを使用する方法と、`Formik`コンポーネントを使用する方法の2種類があります。
今回は`useFormik`フックを使用する方法について確認します。

## Formikの使い方 - フォームの状態管理
### 初期値の定義
Formikに状態管理を任せるには、`useFormik`フックにフォームの初期値を定義します。
```react
const initialValues = {
  fullName: "",
  email: "",
};
/* 中略 */
const { values, handleChange } = useFormik({
  initialValues: {
    fullName: "",
    email: "",
  },
});
```
`useFormik`フックの引数にはオブジェクトを定義します。
様々なプロパティがありますが`initialValues`のみ必須で、それ以外は任意です。

`initialValues`もオブジェクトで定義します。SimpleFormコンポーネントの外側に定義しておきます。
プロパティにはフォームの各入力項目の`name`属性を設定します。
値には初期値を設定します。

また、`useFormik`フックは戻り値としてオブジェクトを返します。
オブジェクトには様々な処理が含まれています。
ここでは状態が設定されている`values`、
フォームの変更に対する処理を行う`handleChange`
を取得しています。

`values`も実態はオブジェクトで、`initialValues`で設定した各項目の状態が含まれています。

### フォームへの反映
取得した状態と変更処理をフォームの各項目に設定します。
```react
<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`をどの項目にも設定します。

### その他処理
本筋とは関係ありませんが、フォームの変更を確認しやすいよう処理を追加しておきます。
```react
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時に各状態を出力するようにしています。

ここまでのソースコードを反映した全文は以下から確認できます。
<details><summary>index.jsの全文</summary>

```react: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 />);


```

</details>

### 結果の確認
各項目に任意の値を入力し、「submit」ボタンを押下すると、コンソールに各ボタンの情報が出力されます。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/d3af5201-41dd-5a01-820a-37bd7a838acd.png'>

このことから、`useFormik`フックから取得した`values`と`handleChange`によって状態の管理が行われていることがわかります。

Formikを使用しない場合`useState`フックを使って状態を定義し、各項目に対して変更を処理するためのハンドラを用意する必要があります。

`useFormk`によって簡潔に状態を管理することができました。

## バリデーションとエラーハンドリング
### バリデーション
バリデーションの方法には2種類あります。
1. 自分でバリデーション用の関数をカスタマイズして、`useFormik`フックの`validate`プロパティに渡す
1. Yupライブラリを使用して、`useFormik`フックの特別なプロパティである`validationSchema`に定義する

Yupのほうが簡潔に書けますし、公式サイトにおいてYupライブラリの使用が推奨されているので、こちらを使います。
ライブラリについては冒頭の環境構築にてインストール済です。

### バリデーションの定義
`SimpleForm`コンポーネントの外に、以下のように定義しておきます。
```react
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フックの修正
```react
  const { values, errors, touched, handleChange, handleBlur } = useFormik({
    /* 初期値の定義 */
    initialValues: {
      fullName: "",
      email: "",
    },
    validationSchema: validationSchema,
  });
```
作成したYupの値を`validationSchema`に設定します。
また、戻り値として`error`を受け取ります。これにはフォームの各項目のエラー情報が格納されています。

そのほか、`touched`と`handleBlur`も取得しておきます。
この2つはフォームのフォーカスの状態を管理するために使用します。
`handleBlur`はフォームからフォーカスが外れたことを検知し、`touched`を`true`にします。

### フォームへの反映とエラーハンドリング
```react
<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`から情報を取得し、フォーカスが外れており、かつエラーメッセージがある場合に、メッセージを出力するようにしています。


ここまでのソースコードを反映した全文は以下から確認できます。
<details><summary>index.jsの全文</summary>

```react: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 />);

```

</details>


### 結果の確認
必須入力の`fullName`にフォーカスを当ててからフォーカスを外すと必須である旨のメッセージが出力されます。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/06bfe9dc-1bf1-6abc-8623-6878dcbba6f2.png'>

デタラメな値をemailに入力してから、フォーカスを外すと形式不正であるメッセージが出力されます。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/777a1833-1bf8-713e-a82c-23149bca8a21.png'>

Yupを使うことで簡単にバリデーションチェックの処理を定義し、`useFormik`フックでフォームの各項目のエラーの状態を管理することができました。

## Formikの使い方 - submit処理
現状の処理だと、フォーカスを外したときにはバリデーションが動きますが、「submit」ボタンを押してもバリデーションは動きません。

また、各項目が未入力の状態でもsubmitされます。（ここではログが出力される。）

これらを解決するためにFormik用のsubmit処理を定義します。
### submit処理の定義
```react
/* submit処理の定義 */
const customSubmit = (values) => {
  console.log(`fullName : ${values.fullName}`);
  console.log(`email : ${values.email}`);
};
```
今まで使っていた`printForm`の処理を削除し、Simpleコンポーネントの外に関数を定義します。
この関数は`useFormik`フックに渡す関数です。
引数としてフォームの値である`values`と`FormikBag`と呼ばれるFomikのメソッドやプロパティを含んだオブジェクトを受け取ります。

### useFormikの修正
```react
const { values, errors, touched, handleChange, handleBlur, handleSubmit } =
useFormik({
  /* 初期値 */
  initialValues: initialValues,
  /* バリデーション */
  validationSchema: validationSchema,
  /* submit処理 */
  onSubmit: customSubmit,
});
```
`onSubmit`プロパティに先ほど定義したsubmit用の関数を設定します。
また、戻り値として`handleSubmit`を追加します。
`onSubmit`プロパティに渡した関数の処理をベースとして、submit時のバリデーションなどを担ってくれる処理です。

### フォームへの反映
```react
<form className="form-container" onSubmit={handleSubmit}>
```
`onSubmit`属性に、`handleSubmit`を設定します。

これでFormikの基本的な機能の実装は異常です。
ここまでのソースコードを反映した全文は以下から確認できます。
<details><summary>index.jsの全文</summary>

```react: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 />);
```

</details>


### 結果の確認

未入力の状態で「submit」ボタンを押すと、バリデーションチェックが行われ、エラーメッセージが表示されます。
また、エラーでsubmit処理が行われないので、コンソールには何も表示されていません。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/73e4e075-ab9a-14fb-0a58-e465446ff0cd.png'>

任意の値を入力して「submit」ボタンを押すと、エラーメッセージは表示されません。
また、submit処理が適切に行われたため、コンソールに値が出力されています。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/c0a3eca5-db13-c977-9b82-ce308343168d.png'>

## まとめ
フォームを簡単に実装することのできるFormikの使い方を確認してきました。
フォームの状態管理、バリデーションとエラーハンドリング、submit時の処理
どれをとっても、自分で実装するよりも簡単に作ることができました。

他にもまだまだ便利なコンポーネントやメソッドがあるようなので、実務を通して学んでいきたいと思いました。
また、学んだ結果を別の機会にまとめようとも思っています。

## 参考にしたサイト
* [Tutorial | Formik](https://formik.org/docs/tutorial)

## おまけ
今回の記事を作成するにあたって、自分でわからなくて調べたり、うまくいかなくて詰まったりしたところをいくつか紹介します。

### 疑問1 : 同じhandleChangeでちゃんと管理できてるの？
**→name属性で判断して別々に管理しているので問題ない** 

それぞれの入力に対して、同じ`handleChange`だったり、`handleBlur`だったりを設定しています。
それでも、各項目に対しての状態管理が正しく行われているようです。これはどういうことなのかよくわからなかったのですが、正解は`name`属性で見分けているようです。

わかりやすく理解するためにFormikを使わずに、`useState`フックで書くと次のようになるとのことでした。
```react
 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側でやっているようです。