【第6回】React初心者入門!useEffectでAPIからデータ取得!読み込み中・エラー表示まで実装しよう

プログラミング

前回の第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(例:

(https://…workers.dev))にリクエストを送ると、ブラウザのセキュリティ機能により CORS(Cross-Origin Resource Sharing)エラー が発生することがあります。

📝よくあるの失敗:初めて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通信を行う際は、以下の流れが基本パターンとなります。

  1. useEffect の第2引数を [] にして初回実行を保証する
  2. loading・error・data の3つの State を用意する
  3. async/await と try-catch-finally で安全に通信処理を書く

次回・第7回では、今回取得したデータ一覧から「特定のIDをクリックして詳細ページへ移動する(useParams / useNavigate)」 実践的なルーティング連携を解説します!

コメント