Masayan tech blog .

React ローディング実装ガイド|react-loadingの使い方とカスタマイズ8選

公開日
最終更新日

要約

Reactアプリケーションにおけるローディング表示の実装には、react-loadingライブラリが最適である。8種類のアニメーションから選択でき、軽量かつシンプルな実装が可能だ。本記事では、基本的なインストールからRedux連携、ユースケース別の実装パターン、カスタマイズ手法、トラブルシューティングまで、実践的な内容を網羅的に解説する。

対象読者: React初心者〜中級者、ローディング実装を検討中の開発者

検証環境: React 18.x、react-loading 2.0.3(2025年11月時点)

この記事を読むことで得られるメリット

この記事を読むことで以下のことが分かる:

  • react-loadingライブラリの基本的な使い方とインストール手順
  • Redux連携を含むローディングコンポーネントの実装方法
  • 8種類のローディングアニメーションの特徴とカスタマイズ手法
  • ユースケース別の実装パターン(API通信、ボタン送信、ページ全体、Suspense連携)
  • 他のローディングライブラリ(react-spinners、react-loader-spinner)との比較と選定基準
  • トラブルシューティングとパフォーマンス最適化の方法

この記事を読むのにかかる時間

約10分

環境

  • MacOS Apple M4 Max Sequoia 15.1
  • React 18.x
  • react-loading 2.0.3
  • Redux Toolkit 1.9.x(オプション)

react-loadingとは

react-loadingは、Reactアプリケーション向けの軽量なローディング表示ライブラリである。8種類の美しいアニメーションを標準搭載し、わずか数行のコードで導入できる手軽さが特徴だ。

主な特徴

  • 軽量: バンドルサイズが小さく、パフォーマンスへの影響が少ない
  • シンプル: 複雑な設定不要で即座に導入可能
  • カスタマイズ性: 色・サイズ・アニメーション種類を柔軟に変更可能
  • TypeScript対応: 型定義ファイルが提供されている

他のローディングライブラリとの比較

ライブラリ

バンドルサイズ

アニメーション数

カスタマイズ性

TypeScript対応

react-loading

小(約15KB)

8種類

react-spinners

中(約30KB)

30種類以上

非常に高

react-loader-spinner

中(約25KB)

15種類

react-loadingを選ぶべきケース:

  • シンプルで軽量なソリューションが欲しい
  • 8種類のアニメーションで十分
  • すぐに導入したい

他のライブラリを選ぶべきケース:

  • より多様なアニメーションが必要(react-spinners)
  • 特殊なデザイン要件がある(react-loader-spinner)

導入手順

ステップ1: インストール

npmまたはyarnでパッケージをインストールする。

npm install react-loading

または

yarn add react-loading

ステップ2: ローディングコンポーネントの作成

以下は、Redux連携を含むローディングコンポーネントの実装例である。

基本的な実装(Redux連携あり)

/src/components/common/Loading.jsx

import React from "react";
import ReactLoading from "react-loading";
import { useSelector } from "react-redux";
import { getIsLoading, getLoadingText } from "../../reducks/loading/selectors";

const Loading = ({ children }) => {
  const selector = useSelector((state) => state);
  const isLoading = getIsLoading(selector);
  const loadingText = getLoadingText(selector);

  if (isLoading) {
    return (
      <section className="flex justify-center items-center h-screen">
        <div>
          <ReactLoading
            type="spin"
            color="#ebc634"
            height="100px"
            width="100px"
            className="mx-auto"
          />
          <p className="text-center mt-3">{loadingText}</p>
        </div>
      </section>
    );
  } else {
    return <>{children}</>;
  }
};

export default Loading;

コードのポイント:

  • isLoadingがtrueの場合、ローディングコンポーネントを表示
  • isLoadingがfalseの場合、childrenをそのまま表示
  • Reduxストアからローディング状態とテキストを取得

シンプルな実装(Redux不要)

Reduxを使用しない場合は、以下のようにローカルステートで実装できる。

import React, { useState } from "react";
import ReactLoading from "react-loading";

const SimpleLoading = () => {
  const [isLoading, setIsLoading] = useState(false);

  const handleClick = async () => {
    setIsLoading(true);
    try {
      // API呼び出しなどの非同期処理
      await fetchData();
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handleClick}>データ取得</button>
      {isLoading && (
        <div className="flex justify-center mt-4">
          <ReactLoading type="spin" color="#3b82f6" height={50} width={50} />
        </div>
      )}
    </div>
  );
};

ステップ3: エントリーポイントでラッパーする

ローディングコンポーネントでアプリ全体をラップすることで、グローバルなローディング表示を実現できる。

/src/index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import Loading from "./components/common/Loading";

if (document.getElementById("root")) {
  ReactDOM.render(
    <Loading>
      <App />
    </Loading>,
    document.getElementById("root")
  );
}

動作の仕組み:

  1. <Loading>コンポーネントが<App />をchildrenとして受け取る
  2. ReduxのisLoadingがtrueの間、ローディング画面を表示
  3. isLoadingがfalseになると、<App />が表示される

ローディングアイコンのカスタマイズ

プロパティ一覧

react-loadingコンポーネントで使用できるプロパティは以下の通りである。

プロパティ名

デフォルト値

説明

type

String

"balls"

アニメーションの種類

color

String

"#ffffff"

ローディングアイコンの色

delay

Number

0

表示開始までの遅延時間(ミリ秒)

height

Number/String

64

高さ(px)

width

Number/String

64

幅(px)

className

String

""

CSSクラス名

アニメーション種類(8種類)

react-loadingでは以下の8種類のアニメーションが利用可能である。

  1. blank - シンプルな空白表示
  2. balls - 複数のボールが跳ねるアニメーション
  3. bars - 縦棒が上下するアニメーション
  4. bubbles - 泡が浮かぶようなアニメーション
  5. cubes - キューブが回転するアニメーション
  6. cylon - 左右に動くスキャンライン
  7. spin - 円形のスピナー(最も一般的)
  8. spinningBubbles - 回転する泡のアニメーション
  9. spokes - 放射状のスポーク

カスタマイズ例

例1: 色とサイズの変更

<ReactLoading
  type="spin"
  color="#3b82f6"  // 青色
  height={80}
  width={80}
/>

例2: 遅延表示

短時間の処理でローディングをチラつかせないため、遅延表示を設定する。

<ReactLoading
  type="bars"
  color="#10b981"
  height={60}
  width={60}
  delay={300}  // 300ms後に表示
/>

例3: カスタムCSSとの組み合わせ

<ReactLoading
  type="spinningBubbles"
  color="#ef4444"
  height="100px"
  width="100px"
  className="custom-spinner"
/>
.custom-spinner {
  filter: drop-shadow(0 0 10px rgba(239, 68, 68, 0.5));
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

ユースケース別の実装パターン

パターン1: API通信時のローディング

import React, { useState, useEffect } from "react";
import ReactLoading from "react-loading";

const DataFetcher = () => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch("/api/data");
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setIsLoading(false);
      }
    };

    fetchData();
  }, []);

  if (isLoading) {
    return (
      <div className="flex justify-center items-center h-64">
        <ReactLoading type="spin" color="#3b82f6" height={60} width={60} />
      </div>
    );
  }

  if (error) {
    return <div>エラー: {error}</div>;
  }

  return <div>{/* データ表示 */}</div>;
};

パターン2: ボタンクリック時のローディング

import React, { useState } from "react";
import ReactLoading from "react-loading";

const SubmitButton = () => {
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async () => {
    setIsSubmitting(true);
    try {
      await submitForm();
      alert("送信完了");
    } catch (err) {
      alert("送信失敗");
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <button
      onClick={handleSubmit}
      disabled={isSubmitting}
      className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded"
    >
      {isSubmitting ? (
        <>
          <ReactLoading type="spin" color="#ffffff" height={20} width={20} />
          送信中...
        </>
      ) : (
        "送信"
      )}
    </button>
  );
};

パターン3: ページ全体のローディング

import React from "react";
import ReactLoading from "react-loading";

const FullPageLoading = ({ isLoading, children }) => {
  if (isLoading) {
    return (
      <div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center z-50">
        <div className="bg-white p-8 rounded-lg shadow-xl">
          <ReactLoading type="spinningBubbles" color="#3b82f6" height={100} width={100} className="mx-auto" />
          <p className="text-center mt-4 text-gray-700">読み込み中...</p>
        </div>
      </div>
    );
  }

  return <>{children}</>;
};

パターン4: React Suspenseとの組み合わせ

import React, { Suspense } from "react";
import ReactLoading from "react-loading";

const LoadingFallback = () => (
  <div className="flex justify-center items-center h-screen">
    <ReactLoading type="cubes" color="#8b5cf6" height={80} width={80} />
  </div>
);

const App = () => {
  return (
    <Suspense fallback={<LoadingFallback />}>
      <LazyComponent />
    </Suspense>
  );
};

Redux Toolkitでの状態管理

最新のRedux Toolkitを使用した実装例を紹介する。

loadingSlice.js

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  isLoading: false,
  loadingText: '読み込み中...'
};

const loadingSlice = createSlice({
  name: 'loading',
  initialState,
  reducers: {
    startLoading: (state, action) => {
      state.isLoading = true;
      state.loadingText = action.payload || '読み込み中...';
    },
    stopLoading: (state) => {
      state.isLoading = false;
      state.loadingText = '';
    }
  }
});

export const { startLoading, stopLoading } = loadingSlice.actions;
export default loadingSlice.reducer;

使用例

import { useDispatch } from 'react-redux';
import { startLoading, stopLoading } from './store/loadingSlice';

const MyComponent = () => {
  const dispatch = useDispatch();

  const fetchData = async () => {
    dispatch(startLoading('データを取得中...'));
    try {
      await api.fetchData();
    } finally {
      dispatch(stopLoading());
    }
  };

  return <button onClick={fetchData}>データ取得</button>;
};

トラブルシューティング

問題1: ローディングが表示されない

原因:

  • isLoadingの状態管理が正しく機能していない
  • コンポーネントが正しくマウントされていない

解決策:

// Redux DevToolsで状態を確認
console.log('isLoading:', isLoading);

// コンポーネントのマウント確認
useEffect(() => {
  console.log('Loading component mounted');
}, []);

問題2: ローディングが消えない

原因:

  • 非同期処理のエラーハンドリング漏れ
  • finallyブロックでの状態更新忘れ

解決策:

const fetchData = async () => {
  setIsLoading(true);
  try {
    await api.call();
  } catch (error) {
    console.error(error);
  } finally {
    // 必ずここで停止
    setIsLoading(false);
  }
};

問題3: アニメーションが動かない

原因:

  • CSSが正しく読み込まれていない
  • z-indexの競合

解決策:

// z-indexを明示的に指定
<div style={{ zIndex: 9999, position: 'relative' }}>
  <ReactLoading type="spin" color="#3b82f6" />
</div>

よくある質問(FAQ)

Q1: React 18のStrictモードで二重レンダリングされる問題は?

A: React 18のStrictモードでは開発時に意図的に二重レンダリングが発生するが、これはreact-loadingの動作には影響しない。本番ビルドでは正常に動作する。

Q2: アニメーションの速度を変更できるか?

A: react-loadingは速度変更のプロパティを提供していないが、CSSアニメーションをオーバーライドすることで調整可能である。

.custom-spinner svg {
  animation-duration: 0.5s !important; /* デフォルトより高速化 */
}

Q3: TypeScriptで型エラーが出る場合は?

A: 型定義ファイルを明示的にインポートする。

import ReactLoading, { LoadingType } from "react-loading";

const type: LoadingType = "spin";

Q4: サーバーサイドレンダリング(SSR)で使えるか?

A: react-loadingはクライアントサイド専用のため、Next.jsなどで使用する場合は動的インポートが必要である。

import dynamic from 'next/dynamic';

const ReactLoading = dynamic(() => import('react-loading'), {
  ssr: false
});

Q5: アクセシビリティ対応は?

A: rolearia-labelを追加することで対応可能である。

<div role="status" aria-label="読み込み中">
  <ReactLoading type="spin" color="#3b82f6" />
  <span className="sr-only">読み込み中...</span>
</div>

パフォーマンス最適化のヒント

1. 遅延表示の活用

短時間で完了する処理の場合、ローディングを一瞬表示するとチラつきが発生する。delayプロパティで300-500ms程度の遅延を設定すると良い。

<ReactLoading type="spin" color="#3b82f6" delay={300} />

2. 条件付きインポート

使用頻度が低い場合は、動的インポートでバンドルサイズを削減する。

const [ReactLoading, setReactLoading] = useState(null);

useEffect(() => {
  if (needsLoading) {
    import('react-loading').then((module) => {
      setReactLoading(() => module.default);
    });
  }
}, [needsLoading]);

3. メモ化

不要な再レンダリングを防ぐため、React.memoを活用する。

const Loading = React.memo(({ isLoading, children }) => {
  if (isLoading) {
    return <ReactLoading type="spin" color="#3b82f6" />;
  }
  return <>{children}</>;
});

まとめ

本記事では、Reactのローディングライブラリ react-loading の導入手順から実践的なカスタマイズ方法まで解説した。

重要なポイント:

  • react-loadingは軽量で導入が簡単なローディングライブラリ
  • 8種類のアニメーションから選択可能
  • Redux連携やローカルステートでの実装パターンを提供
  • カスタマイズ性が高く、色・サイズ・遅延時間を柔軟に調整可能
  • API通信、ボタン送信、ページ全体など様々なユースケースに対応

次のステップ:

  • より多様なアニメーションが必要な場合は react-spinners を検討
  • アクセシビリティ対応を強化する
  • パフォーマンス最適化(遅延表示、動的インポート)を実装

ローディング表示は、ユーザーエクスペリエンスを向上させる重要な要素である。本記事の内容を参考に、プロジェクトに最適なローディング実装を実現されたい。