🗄️ 第十二章:Supabase 動的データベース連携とレンダリング

前章では、究極のビジュアルエフェクトで美しいBento Gridを作成しました。 しかし現在、カード上のテキストはHTMLに「ハードコード」されています。もし上司が新しいキャンプ場を追加したいと言ったら、毎回コードを修正して再デプロイするわけにはいきませんよね?

これが データベース (Database) が必要な理由です。 このレッスンでは、現代最強のBackend-as-a-Serviceである Supabase を使用します。 SQL構文の書き方は教えませんが、「AIに指示を出してSupabaseからデータを取得し、Reactの美しいカードにシームレスに組み込む方法」を教えます!


📡 実践 1:Supabaseクライアント接続の確立

データベースからデータを取得する前に、まず「鍵」を手に入れ、Supabaseに私たちが誰であるかを伝える必要があります。

💡 Vibe Prompt 実践 1:Supabase接続の初期化

[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:

ReactとSupabaseを使ってキャンプ場サイトを開発しています。 Supabase接続を初期化するためのsupabaseClient.jsファイルを作成してください。 URLとKEYは環境変数(.envファイルのVITE_SUPABASE_URLとVITE_SUPABASE_ANON_KEY)に保存されます。 完全なコードを提供し、なぜキーを直接コードに書かずに環境変数に置くのかをコメントで説明してください。

🤖 AIが生成した実際のコード:

// supabaseClient.js
import { createClient } from '@supabase/supabase-js';

// 環境変数からSupabaseのプロジェクトURLと公開APIキーを読み取る
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

// Supabaseクライアントインスタンスを作成してエクスポート
export const supabase = createClient(supabaseUrl, supabaseAnonKey);

/*
【AIセキュリティアドバイス】:
なぜここに直接キーを書いてはいけないのか?(例:"https://xxx.supabase.co"と直接記述)
コードをGitHub(特に公開リポジトリ)にプッシュすると、
世界中のハッカーがあなたのデータベースキーを見ることができ、
すべてのデータを削除または改ざんする可能性があります!
.envファイルに保存し、.gitignoreで.envを除外するのがセキュリティの第一歩です!
*/

🎣 実践 2:データベースからのデータ取得 (Fetch Data)

Supabase内にcampgroundsというテーブルを作成しました。 name(名前)、location(場所)、price(価格)のフィールドがあります。 これらを取得しましょう!

💡 Vibe Prompt 実践 2:非同期取得関数の作成

[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:

先ほど作成したsupabaseClientを使用します。 Supabaseの"campgrounds"テーブルからすべてのデータを取得するasync関数getCampgrounds()を作成してください。 取得が成功した場合はデータの配列(Array)を返してください。 エラーが発生した場合はconsoleにエラーメッセージを表示し、空の配列[]を返して画面がクラッシュしないようにしてください。 詳細な日本語コメントを追加してください。

🤖 AIの防御的プログラミングコード:

import { supabase } from './supabaseClient';

export const getCampgrounds = async () => {
  try {
    // supabase.from('テーブル名').select('*')ですべてのフィールドを取得
    // これは時間がかかる操作なので、必ずawaitを付けます!
    const { data, error } = await supabase
      .from('campgrounds')
      .select('*');

    // SupabaseのAPI設計は独特で、エラーが発生しても直接クラッシュせず、error変数にメッセージが入ります
    if (error) {
      // errorに内容があれば、エラーが発生したと判断し、catchブロックに処理を渡します
      throw error;
    }

    // すべて正常なら、取得したデータ配列を返す
    return data;
    
  } catch (error) {
    // セーフティネット:エラーを捕捉し、警告を表示し、空配列を返す
    console.error('キャンプ場データの取得に失敗 ❌:', error.message);
    return []; 
  }
};

⏳ 実践 3:フロントエンドの状態管理 (State) とローディング (Loading)

これはウェブエンジニアの典型的な面接質問です: 「ネットワーク接続には時間がかかります。この1~2秒の間、ユーザーの画面には何が表示されるべきですか?」 適切に処理しないと、ユーザーには空白の画面が表示され、クラッシュしたと思ってページを閉じてしまいます。 ReactのuseStateuseEffectを使って「ローディング中」の状態を管理しましょう!

💡 Vibe Prompt 実践 3:完全なReactデータフロー

[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:

Reactコンポーネント<CampList>で先ほど作成したgetCampgrounds関数を使用します。 以下の3つの状態(useState)を設定してください: 1. camps(キャンプ場データの配列、デフォルトは空) 2. isLoading(ローディング中かどうか、デフォルトはtrue) 3. errorMsg(エラーメッセージ、デフォルトは空文字) useEffectを使ってコンポーネントの初回レンダリング時にデータを取得してください。 データ取得後、isLoadingをfalseに設定してください。 レンダリングロジック:ローディング中なら"データ拼命ロード中..."を表示、エラーがあればエラーメッセージを表示、問題なければJSON.stringifyでデータを表示してください。

🤖 AIの完璧な状態管理マシン:

import React, { useState, useEffect } from 'react';
import { getCampgrounds } from './api';

export default function CampList() {
  // 状態管理の三兄弟を宣言
  const [camps, setCamps] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [errorMsg, setErrorMsg] = useState("");

  // useEffectの空配列[]は「このコンポーネントが画面に初めて表示された時のみ実行」
  useEffect(() => {
    const fetchData = async () => {
      // 1. データ取得開始、ローディング状態をtrueに
      setIsLoading(true);
      
      // 2. 作成したSupabase APIを呼び出し
      const data = await getCampgrounds();
      
      // 3. データの有無を確認
      if (data && data.length > 0) {
        setCamps(data); // データを状態に保存
      } else {
        setErrorMsg("キャンプ���データが存在しないか、接続に問題があります。");
      }
      
      // 4. 成功失敗にかかわらず、ローディング状態を解除
      setIsLoading(false);
    };

    fetchData();
  }, []);

  // 条件付きレンダリングロジック
  if (isLoading) {
    return <div className="p-10 text-center text-xl text-blue-400 animate-pulse">⏳ データ拼命ロード中...</div>;
  }

  if (errorMsg) {
    return <div className="p-10 text-center text-red-500">❌ {errorMsg}</div>;
  }

  // データ取得成功、生データを表示して確認
  return (
    <div className="p-10">
      <h2 className="text-2xl font-bold mb-4">データベース生データ</h2>
      <pre className="bg-zinc-900 p-4 rounded text-green-400 overflow-auto">
        {JSON.stringify(camps, null, 2)}
      </pre>
    </div>
  );
}

🎨 実践 4:生データを前章の美しいカードに変換

現在、画面には緑色のJSON生データが表示されています。 前章で学んだArray.map()を活用し、各データ項目を美しいグラスモーフィズム光沢カード(GlowCard)に変換しましょう。

💡 Vibe Prompt 実践 4:配列Mapとコンポーネントレンダリング

[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:

現在camps配列には{ id: 1, name: "星空キャンプ場", price: 1500, location: "苗栗" }のようなオブジェクトが含まれています。 <CampList>の最後のreturn部分を修正してください。 1. <div className="grid grid-cols-1 md:grid-cols-2 gap-6">でラップしてください。 2. camps.map()ループを使用してください。 3. 各データを前章で作成した<GlowCard>に渡してください。 4. カード内にキャンプ場名(h3)、場所(p)、価格(p、NT$ xxx / 泊と表示)を表示してください。 Reactリストに必要なkey属性を忘れずに追加してください。

🤖 AIの画竜点睛:

// 元の<pre> JSON出力を削除し、以下に置き換え:
return (
  <div className="max-w-6xl mx-auto p-10">
    <h2 className="text-3xl font-bold text-white mb-8">🏕️ 人気キャンプ場を探索</h2>
    
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
      {/* mapループで各campデータを実際のカードに変換 */}
      {camps.map((camp) => (
        // keyは必須で、Reactが���カードの一意性を認識するために必要
        <GlowCard key={camp.id}>
          <h3 className="text-2xl font-bold text-white mb-2">{camp.name}</h3>
          <p className="text-zinc-400 mb-4 flex items-center gap-2">
            📍 {camp.location}
          </p>
          <div className="mt-auto pt-4 border-t border-white/10">
            <span className="text-emerald-400 font-bold text-xl">NT$ {camp.price}</span>
            <span className="text-zinc-500 text-sm ml-1">/ 泊</span>
          </div>
        </GlowCard>
      ))}
    </div>
  </div>
);

🔍 深層解析: 見てください!これがバックエンドからフロントエンドまでのフルスタック開発です!

  1. Supabaseにテーブルを作成
  2. supabaseClientでデータを取得
  3. useStateで状態を管理し、ローディングを処理
  4. .map()でデータを光沢エフェクト付きHTMLに変換

この4ステップを完了すると、あなたのサイトは真の「ビジネス価値」を持ちます。なぜなら、今後上司が商品を追加したい場合、バックエンドでフォームに入力するだけで、フロントエンドに美しいカードが自動的に表示されるからです。コードを修正する必要は一切ありません!


🚫 究極のトラブルシューティング:無限ループ再び?(useEffectの落とし穴)

Vibe Codingでブラウザをクラッシュさせる最も一般的な原因は、useEffectの依存配列(Dependency Array)の誤った設定です。

💡 Vibe Prompt 実践 5:useEffectの過剰トリガー問題の解決

[!IMPORTANT] 以下のPromptをAIにコピーして送信してください:

ReactコードでuseEffectを使ってSupabaseからデータを取得し、setCampsに保存しています。 しかしブラウザのNetworkタブを見ると、毎秒何百ものAPIリクエストが発生しています!Supabaseから無料枠超過の警告が来ました! 私のコードは以下の通りです: useEffect(() => { fetchData(); }); 問題はどこにあり、どう修正すべきですか?

🤖 AIの緊急ブレーキ救援:

/*
❌ 致命的なエラー分析:
useEffectの最後に「依存配列(Dependency Array)」が記述されていません!
最後の`[]`を省略すると、useEffectのデフォルト動作は「画面が更新されるたびに再実行」になります。
これにより以下の恐ろし��連鎖反応が発生します:
1. useEffectがデータを取得
2. データ取得後、setCampsを呼び出し
3. setCampsが状態を変更し、画面が再レンダリング
4. 画面更新により、再びuseEffectがトリガー(ステップ1に戻る)
これがいわゆる「React無限レンダリング地獄」です!無料API枠を1分で使い切ってしまいます!

✅ 正しい書き方:
useEffectの最後に空配列[]を必ず追加し、「このロジックは初回のみ実行し、その後は画面が変わっても無視する」ことを明示します。
*/

// 修正後の正しいコード:
useEffect(() => {
  fetchData();
}, []); // 👈 この命の空配列!

✅ 本章のまとめとデバッグの極意

この章で、あなたはコーディングエンジニアからデータベース連携可能なフルスタックエンジニアに進化しました!

  1. セキュリティ最優先VITE_SUPABASE_URLは常に.envに保管
  2. 非同期データ取得async/awaitsupabase.from().select()を組み合わせてデータ取得
  3. ユーザー体験向上:空白画面を避け、isLoading状態で優しいローディング表示を
  4. データ変換.map()で生のJSONデータを前章の高級コンポーネントに注入

これまで、私たちはPCの大画面でしかこのサイトを見ていません。しかし現代人の80%はスマートフォンでウェブを閲覧します! 次章では、すべてのエンジニアが頭を悩ませる問題に取り組みます: 第十三章:RWDレスポンシブデザインと魔法のハンバーガーメニュー。Tailwindを使い、一瞬であなたのサイトをすべてのスマホ画面に適応させる方法を教えます!

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!