こんにちは。
長かった投稿機能実装もこれで最後です。今回は投稿フォームを作成していきましょう!
コードについて詳しく解説しているので、疑問点があれば適宜参照して下さい。
投稿フォームを作成
app/components/PostForm.tsx を作成し、以下のコードを入力:
"use client";
import { useState } from "react";
import { useAuth, useUser } from "@clerk/nextjs"; // Clerkの認証情報を取得
export default function PostForm() {
const { getToken } = useAuth(); // ClerkのJWTトークンを取得する関数を取得
const { user } = useUser(); // ユーザー情報を取得
const [content, setContent] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErrorMessage("");
// ユーザーがログインしているかチェック
if (!user) {
setErrorMessage("ユーザーがログインしていません。");
return;
}
// 投稿内容が空でないかチェック
if (!content.trim()) {
setErrorMessage("投稿内容を入力してください。");
return;
}
try {
const token = await getToken({ template: "supabase" }); // Clerk に対してユーザーごとに Supabase 用のトークンを取得するようにリクエスト
if (process.env.NODE_ENV !== "production") {
console.log("送信するトークン:", token);
}
const res = await fetch("/api/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ user_id: user.id, content }),
});
const data = await res.json();
if (res.ok) {
setContent(""); // ✅ 投稿成功時のみリセット
alert("投稿成功!");
} else {
setErrorMessage(data.error || "投稿に失敗しました。");
}
} catch (error) {
console.error("投稿エラー:", error);
setErrorMessage("投稿処理中にエラーが発生しました。");
}
};
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-2">
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="投稿内容を入力..."
className="border p-2"
/>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
投稿
</button>
{errorMessage && <p className="text-red-500">{errorMessage}</p>}
</form>
);
}
このコードでできること:
- ユーザーがテキストを入力できる(textarea)
- 投稿ボタンを押すとデータを送る
- Clerk(認証サービス)でログインしているかチェック
- Supabase の API に投稿データを送信
- 投稿が成功すれば alert(“投稿成功!”) を表示
- エラーがあればエラーメッセージを表示
コードの意味を詳細に解説(クリックして展開)
"use client";
「このコンポーネントはクライアント側(フロントエンド)で実行する」ことを Next.js に伝える
➡ Next.js では、デフォルトではサーバー側で処理されるため、「これはフロントエンド用だよ!」と明示するために use client をつける。
import { useState } from "react";
import { useAuth, useUser } from "@clerk/nextjs";
- useState → 入力した投稿内容やエラーを管理するための React のフック
- useAuth() → Clerk から「ログイン情報」を取得する(JWTトークンを取る)
- useUser() → Clerk から「ユーザー情報」を取得する(ログインユーザーの id など)
const { getToken } = useAuth(); // ClerkのJWTトークンを取得する関数を取得
const { user } = useUser(); // ユーザー情報を取得
const [content, setContent] = useState("");
const [errorMessage, setErrorMessage] = useState("");
コード | 意味 |
---|---|
const { getToken } = useAuth(); | Clerk の useAuth() から getToken() という関数を取得 |
const { user } = useUser(); | Clerk の useUser() から user を取得 |
const [content, setContent] = useState(“”); | 投稿内容を管理する state(変数) |
const [errorMessage, setErrorMessage] = useState(“”); | エラーメッセージを管理する state(変数) |
useState の基本
useState は、「現在の値」と「値を更新する関数」をセットで返す 関数です。
const [state, setState] = useState(初期値);
役割 | 説明 |
---|---|
state | 現在の値(状態) |
setState | state の値を変更する関数 |
useState(初期値) | 状態の初期値をセットする |
handleSubmitの定義
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErrorMessage("");
}
このコードは、投稿フォームの送信ボタンが押されたときの処理(handleSubmit) を定義している。
1. handleSubmit とは?
- handleSubmit は関数(イベントハンドラー)で、フォームが送信されたときに実行される
- 「async」付きなので、非同期処理(await を使う処理)を含む
- フォーム送信のデフォルト動作を防ぎ、エラーメッセージをリセットする!
2. e.preventDefault(); の役割
これは「フォームのデフォルトの送信動作を防ぐ」ためのコード! 🚀
🟢 なぜ必要?
- HTML の form 要素は、送信ボタンを押すと自動でページをリロードする動作がある!
- React では JavaScript でデータを送るので、ページをリロードすると処理が止まってしまう!
- だから、e.preventDefault(); を使ってリロードを防ぎ、React での処理を継続する!
📌 もし e.preventDefault(); をしないと?
- フォームが送信されると、ページがリロードされる(React の状態が消えてしまう!)
- 画面のデータ(useState で管理している content など)がリセットされてしまう!
- 非同期処理(API へのリクエストなど)が動く前に、ページがリロードされてしまう!
✅ e.preventDefault(); を入れることで、React の中でデータ送信処理を行える!
3. setErrorMessage(“”); の役割
これは「エラーメッセージをリセットする」ための処理!
🟢 なぜ必要?
- もし前回の投稿でエラーが発生していた場合、errorMessage にエラーメッセージが残ってしまう。
- 新しい投稿をする前に setErrorMessage(“”) で エラーメッセージを消してリセットする!
サーバーに投稿データを送信するためのリクエスト(fetch)を実行
const res = await fetch("/api/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ user_id: user.id, content }),
});
このコードの流れ
- fetch() が POST リクエストを /api/post に送る
- サーバーがリクエストを処理し、レスポンス(応答)を返す
- そのレスポンスが res に格納される
このコードの fetch() は、次のことをしている。
- /api/post という サーバー側のエンドポイント(API) にデータを送る
- POST リクエストを送る(新しいデータを作成・追加するときに使う)
- headers(リクエストの設定)を追加して、サーバーに正しく情報を伝える
- body(送るデータの中身)を JSON 形式に変換して送信
1. method: “POST” の意味
method: "POST"
📌 リクエストの種類(HTTPメソッド)を「POST」に指定している!
メソッド | 何をする? |
---|---|
GET | データを取得する(読み取る) |
POST | 新しいデータを追加する |
PUT | データを更新する |
DELETE | データを削除する |
✅ このコードでは POST を使って、新しい投稿(content)をサーバーに追加する! 🚀
2. headers: { … } の意味
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
}
この headers は「サーバーに送るデータの設定」です。
サーバーが「どういうデータが送られてくるのか?」を正しく認識するために使います。
✅ “Content-Type”: “application/json” の意味
📌 「このリクエストのデータは JSON 形式ですよ!」とサーバーに伝える設定
✅ Authorization: “Bearer ${token}” の意味
📌 「このリクエストはログイン済みのユーザーからのものですよ!」と証明するための認証情報
💡 ポイント
- Bearer(ベアラー)= 「このトークンを使って認証しますよ!」の意味
- ${token} = getToken({ template: “supabase” }) で取得したトークン
- サーバー側はこのトークンを検証し、「本当にログイン済みのユーザーか?」をチェックする!
✅ この Authorization ヘッダーをつけることで、認証が必要な API にアクセスできる! 🔥
3. body: JSON.stringify({ user_id: user.id, content }) の意味
body: JSON.stringify({ user_id: user.id, content })
📌 リクエストの「本体(送るデータ)」を JSON 形式に変換して送信!
✅ { user_id: user.id, content } の部分
- user_id: user.id → 「どのユーザーが投稿したのか?」
- content → 「投稿の内容(テキスト)」
📌 これを JSON 形式(文字列)に変換して送信する!
✅ JSON.stringify() の役割
- JavaScript のオブジェクト {} を JSON 文字列 “{…}” に変換する!
- サーバー側で req.json() を使って解析できるようにする!
投稿処理の結果を確認
const data = await res.json();
if (res.ok) {
setContent(""); // ✅ 投稿成功時のみリセット
alert("投稿成功!");
} else {
setErrorMessage(data.error || "投稿に失敗しました。");
}
} catch (error) {
console.error("投稿エラー:", error);
setErrorMessage("投稿処理中にエラーが発生しました。");
}
1. const data = await res.json();
📌 サーバーから返ってきたレスポンスを JSON に変換して data に格納する
📌 この data の中には、サーバーが返した message や error などの情報が入っている
2. if (res.ok) { … } 成功時の処理
if (res.ok) {
setContent(""); // ✅ 投稿成功時のみリセット
alert("投稿成功!");
}
📌 res.ok は「リクエストが成功したか?」を判定する
- 成功(res.ok === true)なら、投稿フォームの内容をリセット
- 「投稿成功!」のアラートを表示
🟢 例:投稿が成功した場合
- サーバーが 200 OK(成功)を返す
- res.ok === true になる
- setContent(“”) で投稿フォームを空にする
- alert(“投稿成功!”) でユーザーに通知
✅ 「成功したら、投稿フォームをリセットして、成功メッセージを表示する」 🚀
3. else { … } 失敗時の処理
else {
setErrorMessage(data.error || "投稿に失敗しました。");
}
📌 res.ok === false(エラー発生)の場合は、エラーメッセージを表示する
🟢 data.error があれば、その内容を表示
サーバーが “error”: “タイトルが空です” を返した場合、setErrorMessage(“タイトルが空です”)
🔴 data.error がない場合
“投稿に失敗しました。” というデフォルトのメッセージを表示
✅ 「エラーがあればその内容を表示、なければデフォルトのエラーメッセージを出す!」
4. catch (error) { … } ネットワークエラー時の処理
} catch (error) {
console.error("投稿エラー:", error);
setErrorMessage("投稿処理中にエラーが発生しました。");
}
📌 try 内で何かしらのエラー(例:ネットワーク障害、サーバーが落ちているなど)が起きたら、catch に移動する
投稿フォームの UI(見た目)を定義
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-2">
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="投稿内容を入力..."
className="border p-2"
/>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
投稿
</button>
{errorMessage && <p className="text-red-500">{errorMessage}</p>}
</form>
);
1. <form>(フォーム全体)
<form onSubmit={handleSubmit} className="flex flex-col gap-2">
📌 <form> は「投稿のためのフォーム」を作るタグ
📌 onSubmit={handleSubmit} → フォームを送信したときに handleSubmit を実行する
📌 className=”flex flex-col gap-2″ → Tailwind CSS を使ってデザインを設定
🟢 handleSubmit の動作
- 「投稿」ボタンを押すと、handleSubmit 関数が実行される
- e.preventDefault(); により、ページのリロードを防ぐ
- 投稿内容を API に送信する
✅ この <form> は「投稿ボタンを押したときに、非同期で投稿処理を実行するための仕組み」
2. <textarea>(投稿内容の入力)
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="投稿内容を入力..."
className="border p-2"
/>
📌 <textarea> は「投稿内容を入力するためのボックス」
📌 ユーザーが入力すると content の状態が更新される
✅ value={content}
- content(投稿内容のデータ)が textarea に表示される
- 初期値は “”(空)だが、ユーザーが入力すると更新される
✅ onChange={(e) => setContent(e.target.value)}
- e は「イベントオブジェクト(event)」
- onChange イベントが発生したとき、自動で e(イベントの情報)が渡される
- ユーザーが文字を入力するたびに setContent(e.target.value) が実行される
- content の状態が更新され、画面の textarea に即時反映される
コード | 意味 |
---|---|
e | ✅ イベントオブジェクト(onChange が発生したときの情報) |
e.target | ✅ イベントが発生した要素(この場合は <textarea>) |
e.target.value | ✅ ユーザーが入力した内容(textarea の値) |
setContent(e.target.value) | ✅ content の状態を更新し、入力内容を保存! |
✅ この <textarea> は「ユーザーが投稿内容を入力し、そのデータを content に反映する仕組み」
3. <button>(投稿ボタン)
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
投稿
</button>
📌 <button type=”submit”> → フォームを送信するボタン
📌 className=”bg-blue-500 text-white p-2 rounded” → Tailwind CSS でボタンのデザインを設定
✅ type=”submit”
- ボタンを押すと、<form> の onSubmit(= handleSubmit)が実行される
- つまり、「投稿」ボタンを押すと handleSubmit が動いて、投稿が送信される
✅ この <button> は「投稿を送信するためのボタン」
4. {errorMessage && <p className=”text-red-500″>{errorMessage}</p>}(エラーメッセージ表示)
{errorMessage && <p className="text-red-500">{errorMessage}</p>}
📌 エラーメッセージがあるときだけ、エラーメッセージを表示する
📌 className=”text-red-500″ → Tailwind CSS でエラー文字を赤色にする
✅ errorMessage && <p>{errorMessage}</p> の意味
- errorMessage が “”(空)のとき → 何も表示しない
- errorMessage に “投稿に失敗しました” が入っているとき → 画面にエラーメッセージを表示
投稿フォームをページに表示
src/app/page.tsx を修正
import { SignInButton, SignOutButton, SignedIn, SignedOut } from "@clerk/nextjs";
import PostForm from "./components/PostForm";
export default function Home() {
return (
<main className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-2xl font-bold mb-4">OriginalSNS</h1>
<PostForm />
<SignedOut>
<SignInButton />
</SignedOut>
<SignedIn>
<p>ログイン済み</p>
<SignOutButton />
</SignedIn>
</main>
);
}
現在のディレクトリ構造
現在のディレクトリ構造は以下の通りです。エラーが出る場合はこれを見てディレクトリの構造を確認して下さい。
my-app/
│── .next/ # Next.js のビルドキャッシュ(気にしなくてOK)
│── node_modules/ # インストールされたパッケージ(気にしなくてOK)
│── public/ # 公開用の静的ファイル(画像など)
│── src/ # ここからが実際のコード
│ ├── app/ # Next.jsのルーティング(各ページ)
│ │ ├── page.tsx # ホームページ(トップページ)
│ │ ├── api/ # APIルート(サーバーサイド処理)
│ │ │ ├── post/ # 投稿API
│ │ │ │ ├── route.ts # 投稿データを処理するAPI
│ │ ├── components/ # 再利用可能なUIコンポーネント
│ │ │ ├── PostForm.tsx # 投稿フォーム
│ ├── lib/ # データベース接続やユーティリティ
│ │ ├── supabase.ts # Supabaseの設定
│ ├── middleware.ts. # 認証処理
│── .env.local # 環境変数(APIキー)
│── package.json # プロジェクトの設定
│── tsconfig.json # TypeScriptの設定
│── next.config.js # Next.jsの設定
動作確認
ターミナルから開発サーバーを再起動して、動作を確認します。
npm run dev
- http://localhost:3000 でフォームにテキストを入力し、投稿ボタンを押す。
- Supabase にデータが保存される。
- Supabase → 「Table Editor」→「post」に投稿内容が表示されていればOK!
お疲れ様でした、これでついに投稿とデータベースへの保存ができるようになりました!
次回は投稿の一覧表示を実装していきましょう。
コメント