簡単なフォーム実装まで

useFormikを使う方法が一番簡単だと思います。このフックはフォームを操作する為の様々なハンドラーを返します。以下のように使います。

import {useFormik} from 'formik';

const AComponent = () => {
  const formik = useFormik(options);
  // ...
};

useFormikはいくつらオプションを受け取ります。ここではまだ全て書きませんが、initialValuesonSubmitは必須、validateはオプショナルですがほぼ置いたほうが良いので取り上げます。

initialValuesはそのフォームの初期値でオブジェクトで指定します。例えば連絡フォームであれば、

const initialValues = {
  name: '',
  email: '',
};

のような感じです。既存のデータのものを復元する際は''ではなく、'佐藤 太郎'のような値を置いてあげます。

onSubmitは投稿した際に発火するハンドラーです。ハンドラーは引数としてその時のフォーム値を受け取るので、API に POST したり、Reducer にアクションを流したり様々なことができます。このハンドラーは下記のバリデーションチェックで返すオブジェクトが空(エラーが無い)場合のみ発火されます。

validateはフォーム値のバリデーションチェックを行います。この関数はフォーム内のformik.handleChangeが呼ばれる度実行されます。その関数にはその時のvaluesは引数として渡されるので、値が正しいかどうかを確認できます。この関数で返したオブジェクトはformik.errorsの中身として渡されるので、formik.errorsの対象のプロパティがあるかどうかや、それにエラーメッセージがあるかどうかでエラーメッセージの出し分けができます。

です。

import { useFormik } from "formik";

const AComponent = () => {
  const formik = useFormik({
    initialValues: {
      foo: ""
    },
    validate(values) {
      if (values.foo !== "foo") {
        return { foo: "Please enter `foo`" };
      }

      return {};
    },
    onSubmit: values => {
      console.log(values);
    }
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <label>
        name: <input name="foo" onChange={formik.handleChange} />
        {formik.errors.foo && (
          <span style={{ color: "red" }}>{formik.errors.foo}</span>
        )}
      </label>
      
      <button type="submit">Submit</button>
    </form>
  );
};

ここで使ってるformikプロパティはhandleSubmithandleChangeerrorsの3つです。errorsは上記のバリデーションチェックの辺りに書きました。

formik.handleSubmitを対象の<form>タグに必ず渡します。よしなに Fromik がサイクルを回してonSubmitを発火してくれます。

<input><textarea>タグなど入力を期待する要素にはformik.handleChangeを設定します。このハンドラーを設定するタグにはnameid属性が必ず必要です。Formik はその名前を見て、その名前のvaluesのプロパティ値を更新する為です。

上記のフォームではinput[name=foo]がありますが、nameという値にはバリデーションで'foo'しか渡せないようになってます。なのでaahogeなどと入力するとformik.errors.fooにエラーメッセージが渡る為、その時だけ<span style={{ color: "red" }}>{formik.errors.foo}</span>のような要素を表示するといったことができます。

最初の入力が済むまでエラーメッセージを表示しない

「最初の入力が済むまで」というのは例えば、<input>要素にいくつか入力後、フォーカスが外れた時の事を指します。

formikvalidateformik.handleChangeが呼ばれる度実行されます。なので「'foo'と入力しろ」といバリデーションが設定されていた時、最初に'f'と入力しただけでもformik.errorsにエラーメッセージが入ってしまい、表示してしまいます。これはユーザーにとってはあまりいい体験ではありません。

この体験を改善する方法を Formik は提供しています。それにはformik.touched.[name]値を使います。formik.touchedとはまさにやりたかった「1度はフォーカスが外れた経験をしてるかどうか」を持ちます。この初期値(1度もフォーカスが外れてない時)はfalseになります。

このformik.touchedを使うための準備として<input>formik.handleBlurハンドラーを設定します。このハンドラーもhandleChangeなどと同じ様にnameid属性が必須になります。

[例]です。

<label>
  name:{" "}
  <input
    name="foo"
    onChange={formik.handleChange}
    onBlur={formik.handleBlur}
  />
  {formik.touched.foo && formik.errors.foo && (
    <span style={{ color: "red" }}>{formik.errors.foo}</span>
  )}
</label>

重要なのはエラーメッセージを表示する条件でformik.touched.fooを使っている点です。これにより「1度はフォーカスを外した経験があり、エラーメッセージがあるならそれを表示する」という条件にできます。

Yup を用いたバリデーション

validateで自分で関数を定義するのではなく、validationSchemaYup で定義したスキーマオブジェクトを渡すと規模によっては楽にバリデーションを実装できるかもしれません。

注意点としてvalidationSchemaよりvalidateの方が優先されます。この2つは一緒に設定することがないようにしましょう。

です。

import { useFormik } from "formik";
import * as yup from "yup";

const App = () => {
  const formik = useFormik({
    initialValues: {
      foo: ""
    },
    validationSchema: yup.object().shape({
      foo: yup
        .string()
        .required("必須です")
        .matches(/^foo$/, "fooと入力してください")
    }),
    onSubmit: values => {
      console.log(values);
    }
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <label>
        name: <input name="foo" onChange={formik.handleChange} />
        {formik.errors.foo && (
          <span style={{ color: "red" }}>{formik.errors.foo}</span>
        )}
      </label>

      <button type="submit">Submit</button>
    </form>
  );
};

validationSchemayup.object().shape({...})のような形で作ります。この各プロパティに更にスキーマを設定していきます。ここではfooプロパティの値に対して設定をしてます。

  1. require(message)によって空文字列は駄目
  2. matches(pattern, message)によってfooじゃないと駄目

もし引っかかった時引数のメッセージがformik.errorsに渡されます。これはデフォルトだとfoo must match the following: "/^foo$/"のような日本人やプログラミングが分からない人には分かりづらいメッセージになってしまう為必ず設定すると良いです。

よく使う Yup バリデーション

String

min(n)max(m)両方設定して「n未満とmより大きいのは駄目」なスキーマができます。

yup.string().min(2).max(30)

emailでメールアドレスじゃないと弾かれるスキーマができます。

yup.string().email()

urlで URI な形以外の文字列が弾かれるスキーマができます。

yup.string().url()

Number

min(n)max(m)両方設定して「数値がn未満とmより大きいのは駄目」なスキーマができます。

yup.number().min(10).max(100)

以上と以下の意味にするlessThanmoreThanもあります。

yup().number().lessThan(10).moreThan(100)

Array

input[type=checkbox]などで必須チェックを実装する時に使えます。チェックされた時は['on']、そうでない場合は[]となるので、

yup.array().required()

で確認できます。配列のrequiredは要素数が0だと偽として扱います。

Mixed

mixedを使うと「AかBか…どれかなら大丈夫」といったスキーマを作れます。

oneOfを使うと渡した値のどれかなら大丈夫なスキーマができます。

// input からの場合すべて文字列なので
// 33 (数値)ではバリデーションが通らない
yup().mixed().oneOf(['nju', '33'])