💪

Next.js 14 × Formikによる遷移後の状態消失を考慮したローディングの実装

に公開

はじめに

はじめまして。横浜銀行アジャイル開発チームでエンジニアをしております ひらこ です。

先日ローディング表示の実装を担当したのですが、その際、遷移後のページが表示される前にローディング(※よくあるクルクルと円が回るタイプのアニメーション)が消えてしまうという問題が発覚しました。

本記事は、その際にどのような対応をおこなったのか備忘を兼ねて記録したものになります。

障害事象

前提として我々の環境はこんな感じです。

Next.js 14 × React 19 × Formik 2

Next.js 14 では、ルーティングの非同期処理に関する仕様変更により、router.push() 実行後、遷移中に前ページの状態が失われてしまうという問題がありました。

本記事では、Formik を使ったフォーム送信時に、前ページの状態が消えてしまう現象への対策として複数の状態フラグを組み合わせてローディングを制御する方法を紹介します。

※当時ローディングがすぐに消えてしまう原因が分からなかったとき、こちらの記事に非常に助けられました。この場を借りて感謝申し上げます...🙇

router.push() は非同期ではあるものの、待機 (await) ができません。

なのでフォーム送信後にページ遷移を開始すると、次ページの読み込みが完了する前に前ページの状態(例:送信中フラグなど)がクリアされてしまう問題があります。

送信状態に関する useState が、遷移完了前に unmount されてしまう例を以下に示します。

この場合、前画面が表示されている状態なのに途中でローディングが消えてしまうといった動きになってしまいます。

// 問題のある例
const [isSubmitting, setSubmitting] = useState(false)

const handleSubmit = async (values) => {
  setSubmitting(true)
  try {
    await submitForm(values);
  } finally {
    setSubmitting(false)
  }
  router.push('/next-page'); // 状態を保ったまま遷移したい
};

解決策

この問題を解決するために、Formik の actions オブジェクトにおける setStatus(status) メソッド を使って明示的に状態を管理するようにしてみました。

// サンプルでは useRouter, useState, Formik の利用を想定しています。
const submit = async (values: TestForm, actions: FormikHelpers) => {
    openLoading() // ローディングのモーダルを表示する関数
    actions.setStatus('isSubmitted') // formikのactionsで明示的に状態を設定してあげる
    try {
      const response = await fetch(`/api/test`, { method: 'POST', body: JSON.stringify(values) })
      if (!response.ok) {
        switch (response.status) {
          case 401: {
            actions.setStatus('completed')
            closeLoading()
            // クライアントエラーなら状態を更新してローディングを閉じる
            break
          }
          default:
            throw new Error('api処理に失敗しました')
        }
      } else {
        router.push('next-page')
      }
    } catch {
      actions.setStatus('completed')
      closeLoading()
    }
  }
  • isSubmitted:Formが送信状態。
  • completed:Formの送信が完了した状態。

※openLoadingなどの関数は、適宜ご自身で定義ください。

まとめ

今回私たちのプロジェクトではNext.js 14を使用していたので、 router.push() を使用してページ遷移をおこなう場合、状態の管理に一手間加える必要がありました。

今回はFormik の actions オブジェクトを組み合わせる手法を取りましたが、他にもこんなやり方で回避したよという事例がございましたらぜひ、コメントで教えてください!

横浜銀行(内製担当有志)Tech Blog

Discussion