【15分で作れる】React × OpenAI API のチャットアプリ|基礎から理解する入門ガイド

React × OpenAI API のチャットアプリ

はじめに|ReactでOpenAI APIを利用したアプリを作る理由

React × OpenAI API のイメージ画像

今回は React から OpenAI API を直接呼び出して、シンプルなチャットアプリを作る チュートリアルです。

ゴールは「難しいライブラリや仕組みは使わず、まずは基礎を理解する」こと!

この記事で体験できることは2つです。

  • React の基本(state 管理、イベント処理の流れ)
  • OpenAI API の呼び出し方(fetch を使ったリクエスト)

まずは最小限のコードで “AIと会話できた” という感覚を掴んでいきましょう。

書いたコードは超細かに解説しているのでReact初心者の方も安心して読み進めてください!


React × OpenAI APIで作るチャットアプリの完成イメージ

  • 画面にはチャットログが並ぶ
  • 入力欄に文章を入れて「送信」すると AI が返答してくれる
  • React の基礎と API 呼び出しの両方が学べる

React × OpenAI API のチャットアプリ完成イメージ

0. Reactチャットアプリを作る前に必要な事前準備

  • Node.js(18以上推奨)
  • Vite

上記準備できていない方は以下の記事を参考にインストールしてみてください!
Reactを5分で試せる!初心者向けReact 開発環境構築ガイド

  • OpenAI API キー
    • OpenAI公式にログイン → API Keys ページ
    • 「Create new secret key」で発行
    • sk- で始まる文字列をコピーしておきましょう

1. React + TypeScript環境でチャットアプリ用プロジェクトを作成する

まずは React + TypeScript の環境を作成します。

npm create vite@latest ai-chat -- --template react-ts
cd ai-chat
npm install
  • vite … 最新のフロントエンド開発環境を作るツール
  • react-ts … React + TypeScript のテンプレートを指定

2. ReactプロジェクトにOpenAI APIキーを設定する方法

Vite では環境変数をフロントエンドで使うとき、必ず VITE_ プレフィックスが必要です。

プロジェクト直下に .env ファイルを作り、以下を記載してください。

VITE_OPENAI_API_KEY=sk-ここに自分のAPIキー

3. ReactでチャットUIを実装する(Chatコンポーネントの作成)

ここが本体です。
src/Chat.tsx を新規作成し、以下のコードを貼り付けてください。

import { useEffect, useRef, useState } from "react";

type Msg = { role: "user" | "assistant"; content: string };

export default function Chat() {
  const [messages, setMessages] = useState<Msg[]>([]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState<string | null>(null);
  const endRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    endRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages, loading]);

  async function send() {
    const text = input.trim();
    if (!text || loading) return;

    const next = [...messages, { role: "user", content: text }];
    setMessages(next);
    setInput("");
    setErr(null);
    setLoading(true);

    try {
      const res = await fetch("https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${import.meta.env.VITE_OPENAI_API_KEY}`,
        },
        body: JSON.stringify({
          model: "gpt-4o-mini",
          messages: [
            { role: "system", content: "あなたは親切な日本語アシスタントです。" },
            ...next,
          ],
        }),
      });

      if (!res.ok) {
        const t = await res.text();
        throw new Error(`${res.status} ${res.statusText}\n${t}`);
      }

      const json = await res.json();
      const reply: string = json.choices?.[0]?.message?.content ?? "(応答なし)";
      setMessages((m) => [...m, { role: "assistant", content: reply }]);
    } catch (e: any) {
      setErr(e?.message ?? "エラーが発生しました");
    } finally {
      setLoading(false);
    }
  }

  function onKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
    if (e.key === "Enter" && !e.shiftKey) {
      e.preventDefault();
      send();
    }
  }

  return (
    <div style={{ maxWidth: 720, margin: "40px auto", padding: "0 16px" }}>
      <h1>React × OpenAI シンプルチャット</h1>

      <div style={{
        color: "#333",
        border: "1px solid #ddd",
        borderRadius: 8,
        padding: 16,
        minHeight: 320,
        background: "#fafafa"
      }}>
        {messages.map((m, i) => (
          <div key={i} style={{ whiteSpace: "pre-wrap", margin: "10px 0" }}>
            <b>{m.role === "user" ? "You" : "AI"}:</b> {m.content}
          </div>
        ))}
        {loading && <div>…考え中</div>}
        <div ref={endRef} />
      </div>

      {err && <div style={{ color: "#b00020", marginTop: 8 }}>エラー: {err}</div>}

      <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
        <textarea
          rows={3}
          style={{ flex: 1 }}
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyDown={onKeyDown}
          placeholder="質問を入力(Enterで送信 / Shift+Enterで改行)"
        />
        <button onClick={send} disabled={loading || !input.trim()}>
          {loading ? "送信中…" : "送信"}
        </button>
      </div>
    </div>
  );
}

コード解説(不要な方は飛ばしてください!)

ReactのuseStateとuseRefで状態管理と参照を扱う

const [messages, setMessages] = useState<Msg[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [err, setErr] = useState<string | null>(null);
const endRef = useRef<HTMLDivElement>(null);
  • useState<T>(初期値)
    • React の 状態(state) を作るフック。
    • 戻り値は タプル(配列風の2要素)で、[現在の値, 更新関数] の形。
    • useState<Msg[]>([])<Msg[]>ジェネリクスmessages が「Msg 型の配列」だと 型で約束 しています。
    • setMessages(newValue) を呼ぶと、再レンダリング が起きて UI が最新状態に同期します。
  • messages … 会話ログ({ role: "user" | "assistant"; content: string } の配列)。
  • setMessagesmessages を更新する関数。元配列を直接書き換えず、新しい配列を渡すのが React の鉄則(不変性/イミュータブル)。
  • input … 入力中の文字列。テキストエリアの value と連動(Controlled Component)。
  • loading … API 呼び出し中かどうかのフラグ。二重送信の抑止やローディング表示に使います。
  • err … エラーメッセージ。string | null という ユニオン型。エラーがなければ null
  • useRef<HTMLDivElement>(null)
    • DOM への参照を保持するフック。<HTMLDivElement>参照する要素の型
    • endRef.current で実際の要素を取り出せます。値を変えても再レンダリングは発生しないのが useRef の特徴。
    • 初期値 null にしておき、ref={endRef} を付けた要素がマウントされたら自動的に入ります。

useEffectでチャットログを自動スクロールする

useEffect(() => {
  endRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, loading]);
  • useEffect(副作用関数, 依存配列)
    • レンダリング後に実行したい処理を書く場所(副作用=サイドエフェクト)。
    • ここでは「ログが増えたり、読み込み完了したときに最下部までスクロールする」副作用を実行。
  • endRef.current?.scrollIntoView(…)
    • ?.オプショナルチェイニングcurrentnull ではない時だけメソッドを呼びます(初回レンダリング時の安全策)。
    • scrollIntoView({ behavior: "smooth" }) はブラウザ組み込みの API。対象の要素が見える位置までスクロールします。"smooth" でアニメーション。
  • 依存配列 [messages, loading]
    • ここに挙げた値が 変わったときだけ 副作用が走る。
    • 新しいメッセージが追加されたり、loadingtrue→false に変わった時に、ログの末尾へ自動スクロールします。

OpenAI API へのリクエスト

const res = await fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${import.meta.env.VITE_OPENAI_API_KEY}`,
  },
  body: JSON.stringify({
    model: "gpt-4o-mini",
    messages: [
      { role: "system", content: "あなたは親切な日本語アシスタントです。" },
      ...next,
    ],
  }),
});
  • fetch(url, options)
    • ブラウザの HTTP クライアント関数await でレスポンスを待ちます。
    • method: "POST"headersbody(JSON文字列)を指定して POST リクエストを作成。
  • Authorization: Bearer …
    • OpenAI API へは Bearer トークンで認証。
    • import.meta.env.VITE_OPENAI_API_KEYVite の環境変数.envVITE_ で始まるキー名で書いた値だけがクライアントから参照できます。
    • .env 更新後は 開発サーバーの再起動が必要 です。
  • body: JSON.stringify({…})
    • fetch文字列を送るので、オブジェクトを JSON 文字列に変換します。
    • ここでは Chat Completions の仕様に従い、modelmessages を渡しています。
  • model: “gpt-4o-mini”
    • 軽量で安価、応答も速く、学習用にちょうど良いバランスのモデル。
    • 必要に応じて他モデルにも差し替え可。
  • messages(会話の履歴)
    • 配列で、各要素が { role, content } の形。
    • role"system" | "user" | "assistant" のいずれか。
      • systemAI の性格付け。「日本語で簡潔に」「誤情報を避ける」など。
      • userユーザーからの発言
      • assistantAI の過去の返答(履歴を維持したいときに含める)。
    • ...nextスプレッド構文
      • nextconst next = [...messages, { role: "user", content: text }]; の形で作った 最新の履歴
      • 「これまでのログ」+「今入力したユーザー発言(role: "user")」が展開され、API に渡ります。

Enterで送信 / Shift+Enterで改行

function onKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
  if (e.key === "Enter" && !e.shiftKey) {
    e.preventDefault();
    send();
  }
}
  • イベント型React.KeyboardEvent<HTMLTextAreaElement>
    • TypeScript のジェネリクスで「どの要素からのキーイベントか」を明示。補完が効くようになり安全です。
  • e.key === "Enter"
    • 押されたキーが Enter かどうか。
  • !e.shiftKey
    • Shift キーが押されていないときだけ実行 → Enter 単独で送信。
    • 逆に Shift+Enter のときは return せず ブラウザのデフォルト(改行) を利用。
  • e.preventDefault()
    • Enter で送信する瞬間、テキストエリアの デフォルトの改行キャンセル
    • これがないと「送信」と同時に改行も入ってしまいます。
  • 操作感のまとめ
    • Enter → 送信
    • Shift+Enter → 改行(段落を分けたいときに使う定番の挙動)

4. Reactアプリ全体にChatコンポーネントを組み込む

次に、src/App.tsx を編集してチャットを表示しましょう。

import Chat from "./Chat";

function App() {
  return <Chat />;
}

export default App;

これでアプリ全体に Chat コンポーネントが組み込まれます。


5. Reactチャットアプリを実際に動かしてOpenAI APIと会話する

開発サーバーを起動します。

npm run dev

ブラウザで http://localhost:5173 を開きましょう。

React × OpenAI API のチャットアプリ完成キャプチャ

テキストを入力して送信してみます。

React × OpenAI API のチャットアプリ完成キャプチャ(活用事例1)

少し待つと…

React × OpenAI API のチャットアプリ完成キャプチャ(活用事例2)

回答が返ってきました!!


まとめ|React × OpenAI APIでチャットアプリを作って学べること

React × OpenAI API のチャットアプリを作っている開発者

今回は 「React × OpenAI API」 を使って、シンプルなチャットアプリを作りました。

  • Reactの基本:状態を管理するとUIが自動で更新される
  • OpenAI APIの基本:fetchで呼び出すだけで会話ができる
  • 難しいライブラリは不要。まずは「動かす」体験を大事に!

ここまでで「自分の手でAIを動かせた」という感覚を持てれば大成功です。

次のステップでは、UIを整えたり、過去の会話を保存したりして発展させていきましょう!

関連記事|ReactやGraphQL入門シリーズでさらに学ぶ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です