CodeZine(コードジン)

特集ページ一覧

React向けライブラリを解説~フォームの状態管理を助けるFormikとは?

現場で役立つ! React向けライブラリ詳説 第1回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2020/12/07 11:00
目次

基本の使い方

 それでは、実際の使い方を見ていきましょう。簡単な例として、Webサービスを使い始めるときの申し込みフォームを作ってみます(図2)。

図2:申し込みフォーム
図2:申し込みフォーム

 メールアドレスを入力して送信するだけのシンプルなものです(リスト3)。

[リスト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のようにエラーメッセージを表示します。

図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についてはこちらの記事が詳しいので、よろしければご覧ください。

[リスト4]Formikの2種類のインターフェース
// <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>要素で作り直してみました。

[リスト5]申し込みフォームをuseFormikで作り直す
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>
  )
}

 initialValuesvalidateの設定は全く変えていないので、実質的な変更箇所はJSXの部分のみとなります。FormikのUIコンポーネントを使わなくなったことで、(1)で表示データを適用している部分や、(2)でコールバック関数を登録している部分、(3)でエラーメッセージを表示している部分に変更がありました。リスト3に比べると生々しい書き方にはなりましたが、逆にいえば何をやっているのか見えやすくなりましたね。

 このように、FormikのUIコンポーネントを使わなくても、Formikの状態管理の恩恵を受けることができます。HTML要素だけではなく、他のUIライブラリが提供するコンポーネントと組み合わせながらフォームを組み立てていくとよいでしょう。

Formikのインターフェース

 <Formik>useFormikから受け取れるオブジェクトには、各入力欄の情報やバリデーション結果をはじめとした各種データと、それらに影響を与えるための関数が格納されています。利用できるプロパティの一覧を表1にまとめました。 

表1:Formikで利用できるプロパティ
名前 データ型(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 送信中を表し、handleSubmittrueになった後、 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について解説します。

 



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:現場で役立つ! React向けライブラリ詳説

著者プロフィール

  • WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5