Reactでローディング画面を作る|データ取得中の表示方法

Reactでローディング画面を作成する方法を解説。データ取得中の表示やuseStateを使った実装例を初心者向けに詳しく紹介します。

Learning Next 運営
23 分で読めます

みなさん、こんなことありませんか?

「Reactアプリでデータを取得してる間、画面が真っ白になってしまう」 「ユーザーがアプリが壊れたと思ってしまいそう」

そうなんです。 APIからデータを取得する時に何も表示されないと、ユーザーは「あれ?動いてない?」と不安になってしまいます。

そんな時に必要なのがローディング画面です!

この記事では、Reactでローディング画面を作る方法を詳しく解説します。 useStateを使った基本的な実装から、実践的なパターンまでご紹介しますね。

読み終わる頃には、ユーザーフレンドリーなアプリが作れるようになるはずです!

どうしてローディング画面が必要なの?

まず、なぜローディング画面が重要なのかを理解しましょう。

ユーザーが安心できる

ローディング画面があることで、こんな効果があります。

  • アプリが正常に動いていることが分かる
  • 待機時間があることを理解してもらえる
  • 「処理中です」という安心感を与えられる

適切なローディング画面は、ユーザーの満足度を大幅に向上させるんです。

不安を解消できる

データ取得の間、ユーザーはこんなことを感じています。

  • 画面が止まっているように見える
  • 次に何をすればいいか分からない
  • アプリがクラッシュしたと勘違いしてしまう

ローディング画面で、これらの問題を解決できます。 「今、データを取得中です」と伝えるだけで、全然違うんですよ。

基本的なローディング画面を作ってみよう

useStateを使った基本的なローディング画面の実装方法を解説しますね。

まずは基本のステート管理

ローディング状態を管理するためのstateを定義します。

import React, { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUsers();
  }, []);

  const fetchUsers = async () => {
    try {
      setLoading(true);
      const response = await fetch('/api/users');
      const data = await response.json();
      setUsers(data);
    } catch (error) {
      console.error('データの取得に失敗しました:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      {loading ? (
        <div>読み込み中...</div>
      ) : (
        <ul>
          {users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

このコードを詳しく見てみましょう。

まず、loadingという状態を作っています。 最初はtrueに設定して、「読み込み中」の状態からスタートします。

データ取得の処理では、try-catch-finally文を使っています。

  • try: データを取得する処理
  • catch: エラーが起きた時の処理
  • finally: 成功・失敗に関係なく実行される処理

finallysetLoading(false)を実行することで、必ずローディングを終了できます。

画面の表示部分では、三項演算子を使っています。 loadingtrueなら「読み込み中...」を表示、falseならユーザーリストを表示という仕組みです。

再利用できるローディングコンポーネント

何度も使えるローディングコンポーネントを作ってみましょう。

function LoadingSpinner() {
  return (
    <div className="loading-container">
      <div className="spinner"></div>
      <p>読み込み中...</p>
    </div>
  );
}

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  // データ取得の処理...

  if (loading) {
    return <LoadingSpinner />;
  }

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

専用のローディングコンポーネントを作ることで、再利用性がグッと向上します。 他のページでも同じローディング画面を使えるので、統一感のあるアプリになりますね。

if文を使った書き方もポイントです。 三項演算子より読みやすくなって、コードがすっきりします。

いろんなローディング画面のパターン

ローディング画面には、いくつかの表示パターンがあります。 用途に合わせて選んでみてください。

シンプルなテキスト表示

最もシンプルなローディング画面は、テキストだけの表示です。

function SimpleLoading() {
  return (
    <div style={{ textAlign: 'center', padding: '20px' }}>
      <p>データを読み込んでいます...</p>
    </div>
  );
}

実装が簡単で、どんなデザインにも合わせやすいのが特徴です。 まずはこの形から始めてみるのがおすすめですね。

スピナー(回転アニメーション)

視覚的に分かりやすいスピナーを使用する方法です。

function SpinnerLoading() {
  return (
    <div className="spinner-container">
      <div className="spinner"></div>
      <p>読み込み中...</p>
    </div>
  );
}

対応するCSSはこんな感じです。

.spinner-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 20px;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

このCSSを詳しく説明しますね。

.spinner-containerでは、Flexboxを使って要素を中央に配置しています。 flex-direction: columnで縦方向に並べて、align-items: centerで中央揃えにします。

.spinnerでは、円形のローディングアニメーションを作っています。 borderで枠線を作り、border-topだけ色を変えることで回転効果を表現しています。

@keyframes spinでアニメーションを定義しています。 0度から360度まで回転させることで、くるくる回るアニメーションになります。

プログレスバー

進捗状況を表示できるプログレスバーも効果的です。

function ProgressLoading({ progress }) {
  return (
    <div className="progress-container">
      <div className="progress-bar">
        <div 
          className="progress-fill" 
          style={{ width: `${progress}%` }}
        ></div>
      </div>
      <p>{progress}% 完了</p>
    </div>
  );
}

進捗を数値で表示することで、ユーザーは待機時間を予測しやすくなります。 「あと少しで終わりそう」という感覚を与えられるんです。

プログレスバーは、ファイルアップロードやデータ処理など、進捗が測定できる処理に特に有効ですね。

エラー処理も一緒に実装しよう

実際のアプリケーションでは、エラー処理も重要な要素です。

エラーステートの管理

ローディング、成功、エラーの3つの状態を管理する実装例です。

function DataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const response = await fetch('/api/data');
      
      if (!response.ok) {
        throw new Error('データの取得に失敗しました');
      }
      
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  if (loading) {
    return <LoadingSpinner />;
  }

  if (error) {
    return (
      <div className="error-container">
        <p>エラーが発生しました: {error}</p>
        <button onClick={fetchData}>再試行</button>
      </div>
    );
  }

  return (
    <div>
      {data && (
        <div>
          <h2>{data.title}</h2>
          <p>{data.content}</p>
        </div>
      )}
    </div>
  );
}

このコードで重要なポイントを見てみましょう。

まず、3つの状態を管理しています。

  • data: 取得したデータ
  • loading: ローディング中かどうか
  • error: エラーメッセージ

fetchData関数では、エラーが起きた時にsetError(null)でエラーをクリアしています。 これで、再試行時にエラー表示が残らないようになります。

response.okをチェックして、HTTPエラー(404や500など)も適切に処理しています。 APIが失敗した時も、ユーザーに分かりやすいメッセージを表示できます。

表示部分では、if文を使って状態に応じた画面を出し分けています。 この書き方だと、どの状態でどの画面を表示するかが分かりやすいですね。

再試行機能の実装

エラーが発生した場合の再試行機能も実装しましょう。

function RetryableDataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [retryCount, setRetryCount] = useState(0);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      // データ取得の処理...
    } catch (err) {
      setError(err.message);
      setRetryCount(prev => prev + 1);
    } finally {
      setLoading(false);
    }
  };

  const handleRetry = () => {
    fetchData();
  };

  // レンダリング部分...
}

再試行回数を記録することで、「何回目の試行か」を把握できます。 これで、適切な制御(例:3回失敗したら諦める)が可能になります。

スケルトンローディングを作ってみよう

より洗練されたローディング表示として、スケルトンローディングがあります。

スケルトンコンポーネント

実際のコンテンツの形を模したプレースホルダーを表示します。

function SkeletonCard() {
  return (
    <div className="skeleton-card">
      <div className="skeleton-avatar"></div>
      <div className="skeleton-content">
        <div className="skeleton-title"></div>
        <div className="skeleton-text"></div>
        <div className="skeleton-text short"></div>
      </div>
    </div>
  );
}

function UserCard({ user }) {
  return (
    <div className="user-card">
      <img src={user.avatar} alt={user.name} />
      <div className="user-content">
        <h3>{user.name}</h3>
        <p>{user.email}</p>
        <p>{user.company}</p>
      </div>
    </div>
  );
}

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  return (
    <div>
      {loading ? (
        <>
          <SkeletonCard />
          <SkeletonCard />
          <SkeletonCard />
        </>
      ) : (
        users.map(user => (
          <UserCard key={user.id} user={user} />
        ))
      )}
    </div>
  );
}

スケルトンローディングの仕組みを説明しますね。

SkeletonCardは、実際のUserCardと同じような形状を持っています。 アバター画像の部分、タイトルの部分、テキストの部分をそれぞれ灰色の四角で表現しています。

ローディング中は複数のSkeletonCardを表示して、データが取得できたら実際のUserCardに切り替えます。

この方法の良いところは、ユーザーが「どんなコンテンツが表示されるか」を事前に想像できることです。 突然コンテンツが現れるよりも、自然な感じがしますよね。

スケルトンのCSS

スケルトンローディングのアニメーション効果を追加します。

.skeleton-card {
  display: flex;
  padding: 16px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  margin-bottom: 16px;
}

.skeleton-avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
}

.skeleton-content {
  flex: 1;
  margin-left: 16px;
}

.skeleton-title {
  height: 20px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  margin-bottom: 8px;
}

.skeleton-text {
  height: 16px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: loading 1.5s infinite;
  margin-bottom: 4px;
}

.skeleton-text.short {
  width: 60%;
}

@keyframes loading {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

このCSSのポイントを解説しますね。

linear-gradientで、薄い灰色から濃い灰色へのグラデーションを作っています。 3つの色を指定することで、波のような効果を作り出しています。

background-size: 200% 100%で、背景を実際のサイズの2倍に設定しています。 これで、アニメーションで背景を動かすスペースを確保しています。

@keyframes loadingでは、背景の位置を左右に移動させています。 これで、波が左から右に流れるような効果が生まれます。

この動きが「データを読み込んでいる」感覚を演出してくれるんです。

カスタムフックで管理を楽にしよう

ローディング状態を管理するカスタムフックを作ることで、再利用性を高められます。

useLoadingフックの実装

import { useState, useEffect } from 'react';

function useLoading(asyncFunction, dependencies = []) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const executeAsync = async () => {
      try {
        setLoading(true);
        setError(null);
        const result = await asyncFunction();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    executeAsync();
  }, dependencies);

  const retry = () => {
    executeAsync();
  };

  return { data, loading, error, retry };
}

このカスタムフックの仕組みを説明しますね。

useLoadingは、非同期関数を受け取って、その実行状態を管理してくれます。 データ、ローディング状態、エラー、再試行関数を返してくれるので、とても便利です。

dependencies配列を指定することで、特定の値が変わった時だけ再実行されるようになります。 例えば、ユーザーIDが変わった時だけデータを再取得するといった制御ができます。

カスタムフックの使用例

function UserProfile({ userId }) {
  const { data: user, loading, error, retry } = useLoading(
    async () => {
      const response = await fetch(`/api/users/${userId}`);
      return response.json();
    },
    [userId]
  );

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} onRetry={retry} />;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

カスタムフックを使うと、コンポーネントがとてもすっきりします。

data: userの書き方で、返り値のdatauserという名前で使えるようになります。 これで、コード内で「これはユーザーデータだ」ということが分かりやすくなりますね。

依存関係に[userId]を指定することで、userIdが変わる度に新しいユーザーデータを取得してくれます。

このパターンを使えば、どんなデータ取得処理でも同じように簡潔に書けるようになります。

まとめ:ユーザーに優しいアプリを作ろう

Reactでローディング画面を作る方法について、基本的な実装から実践的なパターンまで詳しく解説しました。

覚えておきたいポイント

  • useStateでローディング状態を管理する
  • エラーハンドリングも同時に実装する
  • スケルトンローディングでユーザー体験を向上させる
  • カスタムフックで再利用性を高める

適切なローディング画面を実装することで、ユーザーフレンドリーなアプリケーションを作成できます。

データ取得中の表示は、ユーザーエクスペリエンスに大きく影響する重要な要素です。 「ちゃんと動いてるよ」ということをユーザーに伝えるだけで、アプリの印象がガラッと変わります。

今日から始めてみよう

ぜひ、今回紹介した方法を参考に、あなたのReactアプリにローディング画面を実装してみてください。

まずはシンプルなテキスト表示から始めて、慣れてきたらスピナーやスケルトンローディングに挑戦してみてくださいね。

きっと、ユーザーの満足度が向上するはずです!

関連記事