2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

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.jsindex.cssを作成します。
image.png

それぞれのファイルは以下の通り記載してください

index.jsの実装
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の実装
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つの作業を楽にしてくれます。

  1. フォームの状態管理
  2. バリデーションとエラーハンドリング
  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}>

printFormikvaluesを使用したいのでコンポーネント内に定義します。
submit時に各状態を出力するようにしています。

ここまでのソースコードを反映した全文は以下から確認できます。

index.jsの全文
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フックから取得したvalueshandleChangeによって状態の管理が行われていることがわかります。

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

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

バリデーションとエラーハンドリング

バリデーション

バリデーションの方法には2種類あります。

  1. 自分でバリデーション用の関数をカスタマイズして、useFormikフックのvalidateプロパティに渡す
  2. 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の使い方について詳しくは触れませんが、fullNameemailそれぞれに必須チェックや最大文字数などを定義しています。
エラー時のメッセージは自動で設定されますが、カスタマイズすることも可能です。

useFormikフックの修正

  const { values, errors, touched, handleChange, handleBlur } = useFormik({
    /* 初期値の定義 */
    initialValues: {
      fullName: "",
      email: "",
    },
    validationSchema: validationSchema,
  });

作成したYupの値をvalidationSchemaに設定します。
また、戻り値としてerrorを受け取ります。これにはフォームの各項目のエラー情報が格納されています。

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

フォームへの反映とエラーハンドリング

<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を設定しました。

また、エラーメッセージの表示処理も追加しています。
fullNameemailそれぞれについて、touchederrorsから情報を取得し、フォーカスが外れており、かつエラーメッセージがある場合に、メッセージを出力するようにしています。

ここまでのソースコードを反映した全文は以下から確認できます。

index.jsの全文
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フックに渡す関数です。
引数としてフォームの値であるvaluesFormikBagと呼ばれる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の全文
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側でやっているようです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?