前回の第5回では、 react-router-dom を使ったページルーティングを解説しました。
今回は、Webアプリ開発に欠かせない「バックエンド(API)からデータを取得して画面に表示する仕組み」を解説します。
ReactでAPI通信を行う際の基本となる useEffect フックの使い方から、実務で必須となる「ローディング表示」や「エラーハンドリング」の実装パターンまで学んでいきましょう!
※本記事で接続するバックエンドAPIは、Cloudflare Workers + Hono + D1 で構築されている前提で進めます。
1. ReactでAPI通信をする時の全体像
React(フロントエンド)からCloudflare D1(データベース)のデータを取得して表示するまでの基本的な流れは以下の通りです。
[React / ブラウザ] [Hono / Workers] [D1 Database]
│ │ │
│ 1. useEffect で fetch() 実行 ─────> │ │
│ │ 2. SQLでデータ取得 ───────> │
│ │ <────── データ返却 ─────────│
│ <───── 3. JSONデータでレスポンス ────│ │
│
4. useState で画面を再描画フロントエンド側の役割は「適切なタイミングでAPIを叩き、返ってきたJSONデータを useState に保存して画面を描くこと」です。
2. なぜ useEffect を使うのか?(無限ループの罠)
「画面が開いた瞬間にデータを取得したい」という場合、コンポーネントの処理の中にそのまま fetch() を書くことはできません。

// ❌ やってはいけない書き方(無限ループが発生する)
export default function UserList() {
const [users, setUsers] = useState([]);
// コンポーネントがレンダリングされるたびに fetch が走り、
// setState されることで再レンダリングが起き、無限ループに陥る!
fetch('https://api.example.com/users')
? .then(res => res.json())
? .then(data => setUsers(data));
return <div>...</div>;
}ここで登場するのが useEffect です。 useEffect を使うことで、「画面が最初に表示された(マウントされた)タイミングで1度だけ実行する」 という制御が可能になります。
// ⭕ 正しい書き方
useEffect(() => {
// 画面が表示された初回1回だけ実行される
fetch('https://api.example.com/users')
? .then(res => res.json())
? .then(data => setUsers(data));
}, []); // ← 第2引数の「空配列 []」が「初回のみ実行」の意味!3. 実践コード:ユーザー一覧を取得するコンポーネント
それでは、Hono+D1からユーザー一覧(/api/users)を取得して表示するコンポーネントを作成してみましょう。
実務では、データ取得中に「読み込み中…」と出したり、APIエラー時に「エラーが発生しました」と出す処理が必須です。これを実現するために 3つの useState を組み合わせて管理します。
サンプルコード (src/components/UserList.jsx)
import { useState, useEffect } from 'react';
export default function UserList() {
// 1. 取得したデータを保持する State
const [users, setUsers] = useState([]);
// 2. 読み込み中かどうかを管理する State
const [loading, setLoading] = useState(true);
// 3. エラー情報を管理する State
const [error, setError] = useState(null);
useEffect(() => {
// 非同期関数を定義して呼び出す
const fetchUsers = async () => {
try {
setLoading(true);
// Hono APIの基本URL(※必要に応じて環境変数化)
const response = await fetch('https://your-worker.workers.dev/api/users');
if (!response.ok) {
throw new Error(`HTTPエラー! status: ${response.status}`);
}
const data = await response.json();
setUsers(data); // 取得したデータを保存
} catch (err) {
console.error('データ取得失敗:', err);
setError('データの取得に失敗しました。');
} finally {
setLoading(false); // 成功・失敗に関わらずローディング終了
}
};
fetchUsers();
}, []); // 初回描画時のみ実行
// ① 読み込み中の表示
if (loading) {
return <p>データを読み込み中...</p>;
}
// ② エラー発生時の表示
if (error) {
return <p style={{ color: 'red' }}>{error}</p>;
}
// ③ 正常にデータが取得できた場合の表示
return (
<div>
<h2>ユーザー一覧</h2>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} ({user.email})
</li>
))}
</ul>
</div>
);
}4. コードの解説ポイント

① 3つの状態(State)による状態管理パターン
Webアプリのデータ取得は必ず 「初期状態 ➔ ローディング中 ➔ 成功または失敗」 の状態を遷移します。
- loading(true / false): くるくるマークや「読み込み中」を制御
- error(null / メッセージ): 通信エラー時のユーザー通知
- users(配列): APIから返ってきたD1のデータ
この3つをセットで管理する手法は、実務開発における王道の共通パターンです。
② async/await は useEffect の中で関数を作って呼び出す
useEffect の直下の関数自体に async をつけることはできません(例: useEffect(async () => …) はNG)。
これは、Reactの仕様上 useEffect の戻り値は「クリーンアップ関数(コンポーネントが消える時の後処理)」として予約されているためです。そのため、内部で const fetchUsers = async () => {…} と関数を定義して実行するのが定石です。
③ .map() で配列データをリスト展開
APIから配列形式([ {id: 1, name: “Alice”}, … ])でデータが返ってくる場合、JSX内では .map() メソッドを使って繰り返し描画します。
※各要素には必ず一意な key プロパティ(key={user.id}) を指定しましょう。
5. 実行時のハマりポイント(CORSエラー)
フロントエンド(例: http://localhost:5173)から、別ドメインである Cloudflare Workers 上の Hono API(例:
📝よくあるの失敗:初めてAPI連携をした時の「CORSエラー」
Reactを学び始め、初めて自作APIと通信させようとした時、ブラウザのコンソールに真っ赤な文字で Access to fetch at … has been blocked by CORS policy と出た時の絶望感は今でも覚えています。
「Reactのコードが間違っているのか?」と何度もコードを確認しましたが、原因はバックエンド側のセキュリティ許可設定漏れでした。CORSは「ブラウザがユーザーを守るための正常な仕組み」だと理解してからは、落ち着いて対処できるようになりました。
💡 解決策:Hono側でCORSミドルウェアを有効にする
CORSエラーはサーバー(バックエンド)側で許可を出すことで解決します。Hono側の src/index.ts にて、以下のように cors ミドルウェアを設定してください。
// Hono側のコード(index.ts)
import { Hono } from 'hono'
import { cors } from 'hono/cors'
const app = new Hono()
// CORSを全許可(またはフロントエンドのURLを指定)
app.use('/api/*', cors())
app.get('/api/users', async (c) => {
// D1から取得する処理...
})まとめ
ReactでAPI通信を行う際は、以下の流れが基本パターンとなります。
- useEffect の第2引数を
[]にして初回実行を保証する - loading・error・data の3つの State を用意する
- async/await と try-catch-finally で安全に通信処理を書く
次回・第7回では、今回取得したデータ一覧から「特定のIDをクリックして詳細ページへ移動する(useParams / useNavigate)」 実践的なルーティング連携を解説します!



コメント