Reactでローディング画面を作る|データ取得中の表示方法
Reactでローディング画面を作成する方法を解説。データ取得中の表示やuseStateを使った実装例を初心者向けに詳しく紹介します。
みなさん、こんなことありませんか?
「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
: 成功・失敗に関係なく実行される処理
finally
でsetLoading(false)
を実行することで、必ずローディングを終了できます。
画面の表示部分では、三項演算子を使っています。
loading
がtrue
なら「読み込み中...」を表示、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
の書き方で、返り値のdata
をuser
という名前で使えるようになります。
これで、コード内で「これはユーザーデータだ」ということが分かりやすくなりますね。
依存関係に[userId]
を指定することで、userId
が変わる度に新しいユーザーデータを取得してくれます。
このパターンを使えば、どんなデータ取得処理でも同じように簡潔に書けるようになります。
まとめ:ユーザーに優しいアプリを作ろう
Reactでローディング画面を作る方法について、基本的な実装から実践的なパターンまで詳しく解説しました。
覚えておきたいポイント
- useStateでローディング状態を管理する
- エラーハンドリングも同時に実装する
- スケルトンローディングでユーザー体験を向上させる
- カスタムフックで再利用性を高める
適切なローディング画面を実装することで、ユーザーフレンドリーなアプリケーションを作成できます。
データ取得中の表示は、ユーザーエクスペリエンスに大きく影響する重要な要素です。 「ちゃんと動いてるよ」ということをユーザーに伝えるだけで、アプリの印象がガラッと変わります。
今日から始めてみよう
ぜひ、今回紹介した方法を参考に、あなたのReactアプリにローディング画面を実装してみてください。
まずはシンプルなテキスト表示から始めて、慣れてきたらスピナーやスケルトンローディングに挑戦してみてくださいね。
きっと、ユーザーの満足度が向上するはずです!