FormikはReact用のフォームバリデーションライブラリです。入力フォームのinput要素に入力した値を取得するだけではなくバリデーション機能なども備えておりReact上で簡単にフォームを実装することができます。バリデーション機能についてはバリデーションライブラリYupを利用することで簡単に実装することができます。入力フォームの作成が嫌いな人もライブラリの力を借りることでフォーム作成の手間を軽減することができます。

本文書ではすぐに実践で活用できるようにバリデーションやエラーメッセージの表示などフォームを作成する上で必要な機能を中心にシンプルなコードを利用して説明しています。

利用するFormikライブラリのバージョンは2です。Reactのバージョンは18.2.0です。

ReactのフォームライブラリにはFormik以外にもさまざまなものが存在します。本ブログではReact Hook Formについての記事を公開しています。

ライブラリを利用しない場合

Formikを使ってフォームを作成する前にライブラリを使用しない場合のReactでのフォームの作成方法について確認しておきます。確認の必要ない人はこの章をスキップしてください。

一般的にはフォームを作成する方法には2つあり、一つはuseState Hookを利用する方法、もう一つはuseRefを利用する方法です。前者はControlled Component, 後者はUncontrolled Componentと呼ばれます。Reactによってinput要素のvalueをどのように制御するかの違いがあります。

本文書ではuseState Hookを利用してemailとpasswordの2つの入力欄を持つログインフォームを作成して動作確認を行います。

useStateを利用したフォーム

useState Hookを利用した場合のコードは下記の通りです。


import './App.css';
import { useState } from 'react';

function App() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({
      email,
      password,
    });
  };

  const handleChangeEmail = (e) => {
    setEmail(e.target.value);
  };
  const handleChangePassword = (e) => {
    setPassword(e.target.value);
  };
  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input id="email" name="email" value={email} onChange={handleChangeEmail} />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={password}
            onChange={handleChangePassword}
            type="password"
          />
        </div>
        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;

useStateでemailとpasswordを定義して初期値は空としています。input要素に文字を入力するとhandleChange関数が実行されてemail、passwordに入力した値が保存されます。ログインボタンをクリックするとhandleSubmit関数が実行されコンソールに入力した値がオブジェクトで表示されます。input要素のvalueの値はuseState(React)によって制御されています。バリデーションの機能は実装していません。

ブラウザで確認すると以下のログイン画面が表示されます。

ログイン画面
ログイン画面

handleChangeEmail, handleChangePasswordを利用せずonChangeイベントに処理を設定することもできます。


<form onSubmit={handleSubmit}>
  <div>
    <label htmlFor="email">Email</label>
    <input id="email" name="email" value={email} onChange={(e) => setEmail(e.target.vallue)} />
  </div>
  <div>
    <label htmlFor="password">パスワード</label>
    <input
      id="password"
      name="password"
      value={password}
      onChange={(e) => setPassword(e.target.vallue)}
      type="password"
    />
  </div>
  <div>
    <button type="submit">ログイン</button>
  </div>
</form>

Formikの設定

ライブラリのインストール

Formikライブラリのインストールはnpmコマンドで行います。


 % npm install formik

useFormikの利用

useFormikのカスタムフックを利用してフォームを作成することができます。App.jsファイルを開いて先ほど確認したuseStateを利用した入力フォームをuseFormik Hookを利用して更新します。useFormik Hookでは引数にオブジェクトを設定することで初期設定を行うことができます。initialValuesにはフォームに利用するフィールドの初期値を設定することができます。onSubmitプロパティには入力した値をvaluesで取得することができます。ここでは動作確認なのでvaluesの値をconsole.logでブラウザのコンソールに表示させていますが通常はバックエンドサーバへの送信などの処理を記述します。useFormik Hookの戻されるオブジェクトをformik変数に保存してformikに含まれるhandleChange関数、handleSubmit関数を利用してフォームを作成しています。


import './App.css';
import { useFormik } from 'formik';

function App() {
  const formik = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
  });

  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={formik.handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            name="email"
            value={formik.values.email}
            onChange={formik.handleChange}
          />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={formik.values.password}
            onChange={formik.handleChange}
            type="password"
          />
        </div>
        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;

App.jsファイルを更新後、入力フィールドに入力を行い、ログインボタンをクリックするとブラウザのコンソールには入力したemailとpasswordの値がオブジェクトで表示されます。

useFormikから戻されるformik変数の中からhandleSubmit, handleChange関数, valuesを利用しましたがその他にはどのような関数やオブジェクトから戻されるか確認します。


function App() {
  const formik = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
  });

   console.log(formik)

//略

ブラウザのコンソールにはformikオブジェクトの中身が表示されます。handleSubmit, handleChange, values以外にもさまざまな関数、オブジェクト、値が戻されることが確認できます。一部の関数や値はこの後の動作確認で利用します。

formikオブジェクトの中身の確認
formikオブジェクトの中身の確認

分割代入を利用することで必要な関数とオブジェクトのみ取り出して下記のようにコードを書き換えることも可能です。


import './App.css';
import { useFormik } from 'formik';

function App() {
  const { handleChange, handleSubmit, values } = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
  });

  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            name="email"
            value={values.email}
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={values.password}
            onChange={handleChange}
            type="password"
          />
        </div>
        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;

またcosole.logを利用してformik.valuesの値を表示させることでフィールドを文字を入力する度にコンポーネントのRe-Render(再レンダリング)が発生することも確認することができます。


function App() {
  const formik = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
  });

   console.log(formik.values)

//略

バリデーションの設定

バリデーションは入力フィールドに入力した値のチェックを行う機能です。例えば一般的にユーザを作成する場合にパスワードの設定が必要となります。パスワードに文字数の制限を行いたい場合にバリデーションを利用することができます。バリデーションを利用することで文字数の制限に達していない場合はサーバへ入力内容を送信する前にユーザにエラーメッセージとして伝えることができます。

バリデーションの設定はvalidate関数を定義してuseFormikの引数に渡すオブジェクトのvalidateプロパティに指定することで設定することができます。


import './App.css';
import { useFormik } from 'formik';

function App() {
    //validate関数を定義
  const validate = (values) => {
    const errors = {};

    if (!values.email) {
      errors.email = '入力が必須の項目です。';
    }
    return errors;
  };

  const { handleChange, handleSubmit, values, errors } = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
    validate,  //validate:validateの略
  });

  console.log(errors);

  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            name="email"
            value={values.email}
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={values.password}
            onChange={handleChange}
            type="password"
          />
        </div>
        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;

validate関数では引数で渡されるvaluesオブジェクトのemailプロパティが値を持っているかチェックを行い、持っていない場合はerrorsオブジェクトにエラーメッセージの追加を行なっています。

入力フォームに文字を何も入力せずにログインボタンをクリックするとブラウザのコンソールに”{email:’入力が必須の項目です。’}”が表示されます。

エラーメッセージの表示

先ほど確認したformikオブジェクトの中身を再度確認するとバリデーションを設定していなかったので空のerrorsオブジェクトを確認することができます。

バリデーションのエラーメッセージをブラウザ上に表示するためにuseFormik戻されるerrorsを利用することができます。


const { handleChange, handleSubmit, values, errors } = useFormik({
  initialValues: {
    email: '',
    password: '',
  },
  onSubmit: (values) => {
    console.log(values);
  },
  validate,
});

バリデーションにエラーが発生した場合にはerrorsオブジェクトのemailプロパティにメッセージが追加されるので分岐を利用してブラウザ上にメッセージを表示させます。


<form onSubmit={handleSubmit}>
  <div>
    <label htmlFor="email">Email</label>
    <input
      id="email"
      name="email"
      value={values.email}
      onChange={handleChange}
    />
    {errors.email && <div>{errors.email}</div>}
  </div>

入力フォームに文字を何も入力せずにログインボタンをクリックするとブラウザ上にエラーメッセージが表示されるようになりました。

エラーメッセージの表示
エラーメッセージの表示

複数のバリデーションの設定

文字列の入力が必須なだけではなくメールアドレスに形式になっているかなど1つのフィールドに複数のバリデーションを設定したい場合は分岐を増やすことで実現することができます。


const validate = (values) => {
  const errors = {};

  if (!values.email) {
    errors.email = '入力が必須の項目です。';
  } else if (
    !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
  ) {
    errors.email = '正しいメールアドレスの形式ではありません。';
  }
  return errors;
};

入力フィールドの文字列を入力すると即座にエラーのメッセージが表示されます。入力した文字列がメールアドレスの形式になっていないためブラウザ上にはエラーメッセージが表示されます。正しいメールアドレスを入力するとメッセージは消えます。

メールアドレスの形式のエラーメッセージ
メールアドレスの形式のエラーメッセージ

emailだけではなくpasswordにもバリデーションを設定したい場合にはvalidate関数にpasswordに関するバリデーションを追加してinput要素の下にエラーメッセージを表示する分岐の処理を追加します。


import './App.css';
import { useFormik } from 'formik';

function App() {
  const validate = (values) => {
    const errors = {};
    if (!values.email) {
      errors.email = '入力が必須の項目です。';
    } else if (
      !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
    ) {
      errors.email = 'Invalid email address';
    }
    if (!values.password) {
      errors.password = '入力が必須の項目です。';
    }

    return errors;
  };
  const { handleChange, handleSubmit, values, errors } = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
    validate,
  });

  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            name="email"
            value={values.email}
            onChange={handleChange}
          />
          {errors.email && <div>{errors.email}</div>}
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={values.password}
            onChange={handleChange}
            type="password"
          />
          {errors.password && <div>{errors.password}</div>}
        </div>
        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;

入力フィールドに中も入力せずログインボタンをクリックするとemailだけではなくpasswordにもエラーメッセージが表示されます。

2つのフィールドでのエラーメッセージの表示
2つのフィールドでのエラーメッセージの表示

バリデーションの内容についてはここまでの説明のように各自でオリジナルの処理を記述することもできますが外部のライブラリを利用することもできます。

Yupライブラリの利用

バリデーションを行うためにバリデーションライブラリYupを利用することができます。Yupライブラリを利用するためにはライブラリのインストールが必要となります。YupはFormik専用のライブラリではなく汎用的なライブラリでReact Hook Formのバリデーションでも利用することができます。


 % npm install yup

Yupではスキーマの定義を行います。スキーマを利用してバリデーションの設定を行います。設定も難しいものではなくバリデーションを行いたいプロパティに対してバリデーションルールを設定していいきます。下記のスキーマではemailフィールドに対してメールアドレスの形式で必須であるバリデーションを設定してpasswordフィールドに対して文字列で必須であるバリデーションでチェックしています。このように複数のバリデーションルールを繋げて記述することができます。


import './App.css';
import { useFormik } from 'formik';
import * as yup from 'yup';

function App() {
  const schema = yup.object({
    email: yup.string().email().required(),
    password: yup.string().required(),
  });

//略

定義したスキーマはuseFormikの引数に渡すオブジェクトのvalidateSchemaプロパティに指定することで設定することができます。ブラウザ上に表示させるメッセージの処理については変更は必要ありません。


import './App.css';
import { useFormik } from 'formik';
import * as yup from 'yup';

function App() {
  const schema = yup.object({
    email: yup.string().email().required(),
    password: yup.string().required(),
  });

  const { handleChange, handleSubmit, values, errors } = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
    validationSchema: schema,
  });

//略

これでYupライブラリを利用してバリデーションの設定は完了です。

動作確認を行うとメッセージは英語ですがメッセージを表示させることができました。

Yupライブラリを利用したエラーの表示
Yupライブラリを利用したエラーの表示

日本語のメッセージ表示

英語のメッセージを表示を日本語に設定した場合には下記のように行うことができます。


const schema = yup.object({
  email: yup
    .string()
    .email('正しいメールアドレスの形式ではありません。')
    .required('入力が必須の項目です。'),
  password: yup.string().required('入力が必須の項目です。'),
});

ブラウザで確認するとメッセージが日本語になっていることが確認できます。

メッセージの日本語化
メッセージの日本語化

その他のバリデーションルール

required, email以外のバリデーションルールについてはYupのGithubのページで確認することができます。

例えばパスワードの文字数の制限は8文字以上32文字以下に設定したい場合はmin, maxのバリデーションルールを利用することができます。


const schema = yup.object({
  email: yup
    .string()
    .email('正しいメールアドレスの形式ではありません。')
    .required('入力が必須の項目です。'),
  password: yup
    .string()
    .min(8, '8文字以上入力してください。')
    .max(32, '32文字以下で入力してください')
    .required('入力が必須の項目です。'),
});

メッセージの表示タイミング

ここまでの設定ではEmailの入力フィールドに文字を入力した瞬間にバリデーションが行われエラーメッセージが表示されます。Emailだけではなくパスワードのフィールドでもエラーメッセージが表示されます。

1文字入力後にメッセージが表示
1文字入力後にメッセージが表示

メッセージを表示するタイミングを変更するためにはuseFormik Hookから戻されるtouchedオブジェクトの値をhandleBlur関数を利用する必要があります。

touchedオブジェクトは名前の通り、入力フィールドにタッチしたかどうかをチェックする仕組みです(input要素にカーソルを合わせたかどうか)。touchedオブジェクトのプロパティの変化を見るためにまずはtouchedオブジェクトのみconsole.logの値を確認します。


  const { touched, handleChange, handleSubmit, values, errors } = useFormik({
    initialValues: {
      email: '',
      password: '',
    },
    onSubmit: (values) => {
      console.log(values);
    },
    validationSchema: schema,
  });

  console.log(touched); //設定

入力フォームで文字を入力してもtouchedオブジェクトの中身は空のオブジェクト({})のままで何も変化はありません。

次にhandleBlurを利用します。


import './App.css';
import { useFormik } from 'formik';
import * as yup from 'yup';

function App() {
  const schema = yup.object({
    email: yup
      .string()
      .email('正しいメールアドレスの形式ではありません。')
      .required('入力が必須の項目です。'),
    password: yup
      .string()
      .min(8, '8文字以上入力してください。')
      .max(32, '32文字以下で入力してください')
      .required('入力が必須の項目です。'),
  });

  const { touched, handleBlur, handleChange, handleSubmit, values, errors } =
    useFormik({
      initialValues: {
        email: '',
        password: '',
      },
      onSubmit: (values) => {
        console.log(values);
      },
      validationSchema: schema,
    });

  console.log(touched);

  return (
    <div className="App">
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            name="email"
            value={values.email}
            onBlur={handleBlur}
            onChange={handleChange}
          />
          {errors.email && <div>{errors.email}</div>}
        </div>
        <div>
          <label htmlFor="password">パスワード</label>
          <input
            id="password"
            name="password"
            value={values.password}
            onBlur={handleBlur}
            onChange={handleChange}
            type="password"
          />
          {errors.password && <div>{errors.password}</div>}
        </div>
        <div>
          <button type="submit">ログイン</button>
        </div>
      </form>
    </div>
  );
}

export default App;
handleBlurを設定した後もhancleChangeを削除してはいけません。

設定後、emailのフィールドに文字を入力してください。文字を入れるたびにコンソールには空のオブジェクトが表示されます。しかしカーソルをフィールドから外すとコンソールには{email: true}が表示されます。パスワードの入力フィールドにもカーソルを合わせて外すと{email:true, password:true}がコンソールに表示されます。

handleBlurとtouchedオブジェクトによって各フィールドにカーソルを合わせたかどうか判断できるようになったのでtouchedの値をメッセージの表示の分岐で利用します。


<form onSubmit={handleSubmit}>
  <div>
    <label htmlFor="email">Email</label>
    <input
      id="email"
      name="email"
      value={values.email}
      onBlur={handleBlur}
      onChange={handleChange}
    />
    {errors.email && touched.email && <div>{errors.email}</div>}
  </div>
  <div>
    <label htmlFor="password">パスワード</label>
    <input
      id="password"
      name="password"
      value={values.password}
      onBlur={handleBlur}
      onChange={handleChange}
      type="password"
    />
    {errors.password && touched.password && <div>{errors.password}</div>}
  </div>
  <div>
    <button type="submit">ログイン</button>
  </div>
</form>

設定後Emailに文字を入れてもメッセージが表示されません。カーソルを外すとバリデーションのエラーが表示されますがパスワードのフィールドが空でもエラーメッセージは表示されません。

Emailのバリデーションのエラーメッセージのみ表示
Emailのバリデーションのエラーメッセージのみ表示

パスワードの入力フィールドにカーソルを合わせて外すとパスワードのエラーメッセージが表示されます。

このようにhandleBlurとtouchedを組み合わせることでバリデーションのエラーメッセージの表示のタイミングを制御することができます。

パスワードのエラーメッセージの表示
パスワードのエラーメッセージの表示

getFieldPropsの利用

各input要素の属性にname, value, onBlur, onChangeを設定していくのは非効率なため効率よく属性を設定できるgetFieldPropsが用意されています。引数にinputのname属性の値を設定することができます。

引数にnameを設定した場合にどのような値が戻されるのか確認します。


console.log(formik.getFieldProps('email'));

name, value, onChange, onBlurが含まれていることが確認できます。

getFieldPropsの中身を確認
getFieldPropsの中身を確認

getFieldPropsを利用する前のinput要素の設定は下記の通りです。


<input
  id="email"
  name="email"
  value={formik.values.email}
  onBlur={formik.handleBlur}
  onChange={formik.handleChange}
/>

getFieldProspを利用すると下記のように記述することができます。かなりシンプルになったことが確認できます。


<input
  id="email"
  {...formik.getFieldProps('email')}
/>

buttonのdisabledの設定

ログインボタンをクリック後の処理中にユーザによる追加のボタンクリックを行わせないようにbuttonをdisabled属性によって無効化することがあります。

FormikではisSubmittingを利用することでbuttonの無効化を制御することができます。isSubmittingの値はuseFormikの戻り値のオブジェクトに含まれており値はtrue, falseのboolean値を持ちます。初期値はfalseです。

isSubmittingはボタンクリックによりuseFormikの引数で設定されているonSubmitが実行されるとtrueに設定されますがバリデーションにエラーがあるとtrueからfalseに自動で戻されます。バリデーションにエラーがない場合はtrueに設定されたままとなります。

実際にbutton要素のdisabled属性にisSubmittingを設定します。ボタンの無効化はdisabledの値がtrueの場合に行われます。


<button type="submit" disabled={formik.isSubmitting}>
  ログイン
</button>

isSubmittingの値の変化が正しく変化しているのか確認するためにバリデーションエラーが発生する場合としない場合でログインボタンをクリックしてください。

バリデーションにエラーがある場合は一時的にボタンは無効化になりますが瞬間の処理なのでブラウザ上では違いわかりません。しかしバリデーションエラーが発生しない場合はログインボタンをクリックすると下記のようにボタンが無効化されることが確認できます。

isSubmittingの値によるボタンの無効化
isSubmittingの値によるボタンの無効化

無効化を解除するためにはtrueになったisSubmittingの値をfalseに戻す必要があります。isSubmittingの値の更新は、useFormikのonSubmitの中で行うことができます。onSubmitの引数にはvalues以外にformikBagも持ち、formikBagの中にsetSubmitting関数が含まれています。setSubmitting関数を利用することでtrueからfalseにisSubmittingの値を変更することができます。


const formik = useFormik({
  initialValues: {
    email: '',
    password: '',
  },
  onSubmit: (values, { setSubmitting }) => {
    console.log(values);
    setSubmitting(false);
  },
  validationSchema: schema,
});

現在の設定は同期処理ですがonSubmitの中ではサーバへのリクエストを行うため通常は非同期処理が行われます。Promiseを利用して非同期処理を追加します。


const formik = useFormik({
  initialValues: {
    email: '',
    password: '',
  },
  onSubmit: async (values, { setSubmitting }) => {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    console.log(values);
  },
  validationSchema: schema,
});

非同期処理を追加することでsetSubmitting関数は利用していませんがsetTimoutで設定した1秒後にログインボタンはクリック可能になります。その理由はドキュメントに記述されており、非同期処理を実行した場合はformikが自動でisSubmittingの値をfalseに設定してくれるようです。

説明はFormikのドキュメントのonSubmitの説明に記述されています。同期の時は手動でsetSubmitting(false)を設定する必要があるとも記述されているので実際の動作と一致しています。

“IMPORTANT: If onSubmit is async, then Formik will automatically set isSubmitting to false on your behalf once it has resolved. This means you do NOT need to call formikBag.setSubmitting(false) manually. However, if your onSubmit function is synchronous, then you need to call setSubmitting(false) on your own.”

isSubmittingの値を利用することで複数回のボタンクリックを防げるようになりました。

バリデーションエラー時のButtonの無効化

先ほどはボタンをクリックした後の処理中でのボタンの無効化でしたがバリデーションがエラーの間はボタンをクリックさせてくないという場合もあるかと思います。その場合には Formikが持つ他のプロパティの値を利用することができます。

一つはdirtyで初期値と同じ値が入力されている場合はfalseになりますが異なる値が入力されている場合はtrueとなります。もう一つの値はisValidでバリデーションがエラーの場合はfalseとなりますがバリデーションにエラーがない場合はtrueとなります。つまり両方の値がtrueの場合はボタンは有効でどちらからfalseの場合は無効化させる必要があります。


<button
  type="submit"
  disabled={!(formik.dirty && formik.isValid) || formik.isSubmitting}
>
  ログイン
</button>

ページを開いた時からログインボタンは無効化されており、emailとpasswordのフィールドに入力を行い、バリデーションエラーがなくなるとボタンが有効化されクリックすることが可能となります。クリックするとisSubmittingの値がtrueになるためボタンは無効化され非同期処理が完了すると自動でボタンは有効化されます。

本文書で説明した以外にもuseFormikのオプションやuseFormikから戻されるオブジェクトのプロパティはありますがFormikのuseFormikを利用する上で必要な基礎は理解できたかと思います。ぜひFormikを利用してフォームを作成してみてください。