Reactでフォーム送信時にローディング状態を実装する方法

Reactでフォーム送信中にローディング状態を表示する方法を理解することは、優れたユーザー体験を提供するうえで非常に重要です。フォーム送信中に何もフィードバックがないと、ユーザーは送信が成功しているのか、失敗しているのか分からず、不安を感じることがあります。本記事では、Reactでローディング表示を実装するための基本的な方法から、実際のコード例を用いた応用的な実装までを詳しく解説します。このガイドを通じて、ユーザーがスムーズに操作できるフォームを構築するための知識を習得しましょう。

目次

フォーム送信中のローディング表示の重要性

ユーザー体験の向上


フォーム送信中にローディング状態を表示することは、ユーザーに処理中であることを伝える重要な手段です。これにより、ユーザーはシステムが応答していると認識でき、不必要にフォームを再送信したり、ページを離れたりすることを防げます。

信頼感の向上


ローディングインディケーターが表示されることで、ユーザーは操作の結果を待つ理由を明確に理解できます。これは、アプリケーション全体の信頼性やプロフェッショナルさを示すことにも繋がります。

フォーム送信時の課題解消


ローディング表示がない場合、以下のような問題が発生する可能性があります。

  • 送信完了まで待たずにページを閉じてしまう。
  • 同じデータが複数回送信される。
  • フォーム送信中に誤操作をしてしまう。

これらの課題を解消するためにも、ローディング表示は欠かせない要素です。

基本的なReactフォームの構築

Reactフォームの概要


Reactでフォームを作成する場合、基本的には以下の手順で進めます。

  1. 必要な状態(State)の作成
  2. フォームコンポーネントの作成
  3. イベントハンドラーを用いた送信処理の追加

以下に、シンプルなReactフォームの例を示します。

Reactフォームのコード例

import React, { useState } from 'react';

function BasicForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

export default BasicForm;

コードのポイント

  1. useStateを利用して、フォームの状態を管理しています。
  2. handleChange関数で、フォームの入力データをリアルタイムで更新します。
  3. handleSubmit関数で、送信イベントを処理し、フォームのデータを扱います。

このフォームを基に、次のステップでローディング状態を管理する機能を追加していきます。

ローディング状態を管理するためのStateの導入

ローディング状態の必要性


フォーム送信処理が進行中であることをユーザーに知らせるため、ローディング状態を管理するStateを導入します。このStateを使用して、フォームの送信中かどうかを簡単に切り替えることができます。

ローディングStateの追加


以下に、先ほどの基本フォームにローディング状態を管理する機能を追加した例を示します。

import React, { useState } from 'react';

function LoadingForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isLoading, setIsLoading] = useState(false); // ローディング状態の管理

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true); // ローディング状態をtrueに設定
    try {
      // ダミーの送信処理
      await new Promise((resolve) => setTimeout(resolve, 2000)); // 2秒待機
      console.log('Form submitted:', formData);
    } catch (error) {
      console.error('Error during form submission:', error);
    } finally {
      setIsLoading(false); // ローディング状態をfalseに戻す
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          disabled={isLoading} // ローディング中は入力を無効化
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          disabled={isLoading} // ローディング中は入力を無効化
        />
      </div>
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
}

export default LoadingForm;

実装のポイント

  1. isLoading State: フォーム送信中の状態を管理するために新たなStateを追加しました。
  2. setTimeoutによる非同期処理: 実際のサーバー通信を想定して、非同期処理のモックとして2秒待機する処理を実装しました。
  3. ローディング中のボタン表示: ローディング中に「Submitting…」と表示することで、ユーザーに進行状況を明示します。
  4. フォーム無効化: ローディング中にフォーム入力を無効化し、ユーザーの誤操作を防止します。

次のステップでは、送信ボタンにローディングインディケーターを追加し、視覚的なフィードバックを強化します。

ボタンにローディングインディケーターを追加

ローディングインディケーターの必要性


フォーム送信中にボタンの見た目を変え、ローディング状態を視覚的に伝えることで、ユーザーに明確な進行状況を示すことができます。これにより、操作の成功をより分かりやすくアピールできます。

ローディングインディケーターの実装


以下のコードでは、ReactのローディングStateを使用して送信ボタンにスピナー(インディケーター)を追加しています。

import React, { useState } from 'react';

function LoadingButtonForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isLoading, setIsLoading] = useState(false);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    try {
      // ダミー送信処理
      await new Promise((resolve) => setTimeout(resolve, 2000)); // 2秒待機
      console.log('Form submitted:', formData);
    } catch (error) {
      console.error('Error during form submission:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <button type="submit" disabled={isLoading}>
        {isLoading ? (
          <>
            <span className="spinner"></span> Submitting...
          </>
        ) : (
          'Submit'
        )}
      </button>
      <style jsx>{`
        .spinner {
          border: 2px solid #f3f3f3;
          border-top: 2px solid #3498db;
          border-radius: 50%;
          width: 12px;
          height: 12px;
          animation: spin 1s linear infinite;
          display: inline-block;
          margin-right: 8px;
        }

        @keyframes spin {
          0% {
            transform: rotate(0deg);
          }
          100% {
            transform: rotate(360deg);
          }
        }
      `}</style>
    </form>
  );
}

export default LoadingButtonForm;

実装のポイント

  1. ローディングスピナーの追加: ボタン内にインディケーター(スピナー)を配置し、ローディング中であることを視覚的に示します。
  2. <style jsx>によるスピナーのアニメーション: CSSアニメーションを使用してスピナーを回転させ、動的な表示を実現しています。
  3. ローディング時のテキスト変更: ボタンテキストが「Submit」から「Submitting…」に変わることで、進行状況をさらに明確に伝えます。

視覚効果の改善


ローディングインディケーターを使用することで、ユーザーに処理中であることを直感的に伝えることができます。また、CSSアニメーションを利用することで、エレガントな見た目を実現しています。次のステップでは、非同期処理とローディング状態の連携についてさらに詳しく解説します。

非同期処理とローディング状態の連携

非同期処理の概要


フォーム送信では、多くの場合、サーバーへのリクエストやデータベースへの書き込みなど、非同期処理が必要です。この際、ローディング状態を適切に管理することで、ユーザーが操作結果を正しく理解できるようにします。

非同期処理とStateの連携


以下のコードでは、非同期処理を用いたフォーム送信とローディング状態の管理方法を示します。

import React, { useState } from 'react';

function AsyncForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isLoading, setIsLoading] = useState(false);
  const [responseMessage, setResponseMessage] = useState(''); // サーバーの応答を表示

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    setResponseMessage(''); // 応答メッセージをリセット
    try {
      // ダミーAPI呼び出し
      const response = await new Promise((resolve) =>
        setTimeout(() => resolve({ success: true, message: 'Form submitted successfully!' }), 2000)
      );

      if (response.success) {
        setResponseMessage(response.message); // 成功メッセージを設定
      } else {
        setResponseMessage('An error occurred while submitting the form.');
      }
    } catch (error) {
      setResponseMessage('Error: Unable to submit the form.');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Submitting...' : 'Submit'}
      </button>
      {responseMessage && <p>{responseMessage}</p>}
    </form>
  );
}

export default AsyncForm;

実装の詳細

  1. 非同期処理のモック: setTimeoutを使って、APIリクエストを模した非同期処理を行っています。実際のプロジェクトではfetchaxiosを使用してサーバーにリクエストを送ります。
  2. responseMessage State: サーバーの応答メッセージを表示するために、新たにresponseMessageを管理しています。これにより、送信が成功したかどうかをユーザーに伝えられます。
  3. ローディングと非同期処理の連携: 非同期処理の開始時にisLoadingtrueに、完了時にfalseに切り替えることで、ローディング状態を制御しています。

エラー時の対応


送信エラーが発生した場合でも、適切にメッセージを表示してユーザーにフィードバックを提供します。このように、非同期処理とローディング状態を連携させることで、ユーザー体験を向上させることができます。

次のステップでは、エラー処理とローディング解除に焦点を当て、さらなる改善方法を解説します。

エラー処理とローディング解除

エラー処理の重要性


フォーム送信時には、ネットワークの問題や入力データの不備など、さまざまな理由でエラーが発生する可能性があります。これに対する適切なエラー処理と、ローディング状態の解除は、ユーザーがシステムを信頼するために重要です。

エラー処理を追加したフォームの実装


以下は、エラー時にユーザーにメッセージを表示し、ローディングを解除するフォームの例です。

import React, { useState } from 'react';

function ErrorHandlingForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isLoading, setIsLoading] = useState(false);
  const [errorMessage, setErrorMessage] = useState(''); // エラーメッセージの管理
  const [successMessage, setSuccessMessage] = useState(''); // 成功メッセージの管理

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    setErrorMessage('');
    setSuccessMessage('');
    try {
      // ダミーAPI呼び出し
      const response = await new Promise((resolve, reject) =>
        setTimeout(() => {
          if (formData.email.includes('@')) {
            resolve({ success: true, message: 'Form submitted successfully!' });
          } else {
            reject(new Error('Invalid email address'));
          }
        }, 2000)
      );

      if (response.success) {
        setSuccessMessage(response.message);
      }
    } catch (error) {
      setErrorMessage(error.message || 'An unexpected error occurred.');
    } finally {
      setIsLoading(false); // ローディング状態を解除
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Submitting...' : 'Submit'}
      </button>
      {errorMessage && <p style={{ color: 'red' }}>{errorMessage}</p>}
      {successMessage && <p style={{ color: 'green' }}>{successMessage}</p>}
    </form>
  );
}

export default ErrorHandlingForm;

実装のポイント

  1. エラーメッセージの管理: errorMessage Stateを使用してエラー内容を保持し、ユーザーに明確なフィードバックを提供します。
  2. 成功メッセージの分離: 成功した場合のメッセージをsuccessMessageで管理し、エラー時と成功時の表示を区別します。
  3. ローディング解除の確実化: エラーが発生してもfinallyブロック内でsetIsLoading(false)を呼び出すことで、ローディング状態が解除されるようにします。

エラーメッセージの改善


エラーメッセージをユーザーが簡単に理解できるようにすることが重要です。たとえば、「Invalid email address」のように具体的な理由を示すことで、ユーザーが問題を修正しやすくなります。

この方法を用いることで、エラー時の混乱を最小限に抑え、ユーザーの信頼感を高めることができます。次のステップでは、さらなるユーザー体験向上のための追加の工夫を紹介します。

ユーザー体験を向上させる追加の工夫

ローディング表示のアニメーション化


ローディング中の視覚的なフィードバックを強化するために、アニメーションを取り入れることで、フォームの操作をより直感的に感じられるようにします。これにより、ローディング中もユーザーの注意を引き、操作結果を待ちやすくなります。

以下に、アニメーションを追加した例を示します。

import React, { useState } from 'react';

function EnhancedForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isLoading, setIsLoading] = useState(false);
  const [responseMessage, setResponseMessage] = useState('');

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    setResponseMessage('');
    try {
      // ダミーの非同期処理
      await new Promise((resolve) => setTimeout(resolve, 2000));
      setResponseMessage('Form submitted successfully!');
    } catch (error) {
      setResponseMessage('Error occurred while submitting the form.');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <button type="submit" disabled={isLoading}>
        {isLoading ? (
          <>
            <span className="spinner"></span> Submitting...
          </>
        ) : (
          'Submit'
        )}
      </button>
      {responseMessage && <p>{responseMessage}</p>}
      <style jsx>{`
        .spinner {
          border: 4px solid #f3f3f3;
          border-top: 4px solid #3498db;
          border-radius: 50%;
          width: 16px;
          height: 16px;
          animation: spin 1s linear infinite;
          display: inline-block;
          margin-right: 8px;
        }

        @keyframes spin {
          0% {
            transform: rotate(0deg);
          }
          100% {
            transform: rotate(360deg);
          }
        }
      `}</style>
    </form>
  );
}

export default EnhancedForm;

追加のスタイリングとフィードバック

  1. アニメーションスピナー: CSSアニメーションでスムーズな回転を実装し、ローディング中であることを視覚的に示します。
  2. レスポンスの視覚化: 成功時や失敗時にメッセージが色分けされることで、ユーザーが結果を一目で確認できます。

アクセシビリティの向上


ローディングインディケーターやフィードバックメッセージにaria-livearia-busy属性を追加すると、画面リーダーを使用するユーザーにも状態を伝えやすくなります。

<p aria-live="polite">{responseMessage}</p>
<button aria-busy={isLoading}></button>

操作性の強化

  • ローディング中に他のボタンやフォーム入力を無効化することで、誤操作を防止します。
  • ローディング中は背景を薄暗くし、モーダル形式でユーザーの注意をローディング状態に集中させる工夫も効果的です。

このような工夫を加えることで、より直感的で使いやすいフォームを提供でき、ユーザーの満足度を大きく向上させることができます。次は、実践的なフォーム送信機能を実装した完全な例を紹介します。

実践:リアルなフォーム送信機能の例

リアルなフォーム送信機能の要素


このセクションでは、以下の要素を組み込んだ実践的なフォーム送信機能を実装します。

  • 非同期API呼び出し: fetchを使用してデータを送信します。
  • ローディング表示: ボタンのローディング状態を可視化します。
  • エラーと成功のメッセージ表示: 状態に応じた適切なフィードバックを行います。

完全な実装コード

import React, { useState } from 'react';

function RealWorldForm() {
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [isLoading, setIsLoading] = useState(false);
  const [errorMessage, setErrorMessage] = useState('');
  const [successMessage, setSuccessMessage] = useState('');

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    setErrorMessage('');
    setSuccessMessage('');
    try {
      // 本物のAPI呼び出し(ここではダミーURLを使用)
      const response = await fetch('https://example.com/api/submit', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(formData),
      });

      if (!response.ok) {
        throw new Error('Failed to submit the form.');
      }

      const result = await response.json();
      setSuccessMessage(result.message || 'Form submitted successfully!');
    } catch (error) {
      setErrorMessage(error.message || 'An unexpected error occurred.');
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          disabled={isLoading}
        />
      </div>
      <button type="submit" disabled={isLoading}>
        {isLoading ? (
          <>
            <span className="spinner"></span> Submitting...
          </>
        ) : (
          'Submit'
        )}
      </button>
      {errorMessage && <p style={{ color: 'red' }}>{errorMessage}</p>}
      {successMessage && <p style={{ color: 'green' }}>{successMessage}</p>}
      <style jsx>{`
        .spinner {
          border: 4px solid #f3f3f3;
          border-top: 4px solid #3498db;
          border-radius: 50%;
          width: 16px;
          height: 16px;
          animation: spin 1s linear infinite;
          display: inline-block;
          margin-right: 8px;
        }

        @keyframes spin {
          0% {
            transform: rotate(0deg);
          }
          100% {
            transform: rotate(360deg);
          }
        }
      `}</style>
    </form>
  );
}

export default RealWorldForm;

ポイント解説

  1. fetchによるAPI通信
    fetch関数を用いて非同期でフォームデータを送信します。response.okでHTTPステータスコードをチェックし、エラーを適切にハンドリングしています。
  2. ローディング状態の可視化
    isLoadingを利用して、送信中はボタンにスピナーを表示し、ユーザーに処理中であることを示します。
  3. 成功・エラーメッセージの表示
    successMessageerrorMessageを分けて管理することで、明確なフィードバックを提供します。

リアルな利用シナリオでの応用

  • フォーム検証: 送信前にクライアントサイドで入力チェックを行い、エラーがあれば早期に通知します。
  • 複数のフィールド管理: より多くのフィールドを追加して複雑なフォームを実装します。
  • APIとの統合: 実際のバックエンドサービスと統合することで、リアルなユースケースをサポートします。

この実装は、Reactで実際のプロジェクトに利用可能なフォーム送信機能のテンプレートとして活用できます。次は、全体を振り返りながら本記事のまとめを行います。

まとめ


本記事では、Reactでフォーム送信時にローディング状態を表示する方法について、基礎から実践まで解説しました。ローディング状態を管理するStateの導入から、非同期処理との連携、エラー処理、そして視覚的に優れたインターフェースの実現まで、ユーザー体験を向上させる方法を具体的に説明しました。

最終的に、実践的なフォーム送信機能を構築することで、開発者が直面する課題を解決するための実装例を示しました。この知識を活用することで、ユーザーに信頼されるアプリケーションを構築する助けとなるでしょう。

Reactでのフォーム管理とローディング表示の技術をマスターし、プロジェクトに活かしてください!

コメント

コメントする

目次