📝

【開発ログ】単語カードをAPI経由で取得して一覧表示まで実装

に公開

単語カード一覧の取得まで進みました。まだ自分が思い描いている完成形には遠いですが、
大きな流れはできてきたので、これからは細かい UI/UX の改善やデータ加工、エラー処理に注力していきます。

前回

  • タグ機能(検索・フィルタリング対応も検討)
  • Word 登録時に既存タグを選択 or 新規タグを作成できるよう修正
  • タグ一覧をフロントから取得して UI で選択可能に

今回

  • 単語カードを API から取得

① 単語カードを API から取得

まずはログインユーザが登録した単語カードを API 経由で取得できるようにしました。
Prisma で Word を検索し、必要なフィールドだけ DTO に整形して返すように実装。

export async function GET() {

  /* const user = "セッションクッキーを使ってuser情報取得”**/

  const wordList = await prisma.word.findMany({
    where: { userId: user.id },
    include: { image: true, tags: true },
    orderBy: { createdAt: "asc" },
  });

    //必要なデータの整形
  const dto = wordList.map((w) => ({
    id: w.id,
    ja: w.jaSurface,
    ko: w.koSurface,
    imgUrl: w.image?.imageUrl ?? "https://via.placeholder.com/300x200",
    tags: w.tags.map((t) => t.name),
    status: w.status,
  }));

  return NextResponse.json(dto);
}

② フロントでの取得と表示

フロントエンド側では useEffect を使って /api/learn からカード一覧を取得。
取得した配列を map で描画するだけで、ユーザーの単語カード一覧を表示できるようになりました。

"use client";
import { useEffect, useState } from "react";
import { LearningCardData } from "@/types/lesson";

export default function LearningCardList() {
  const [cards, setCards] = useState<LearningCardData[]>([]);

  useEffect(() => {
    const fetchCards = async () => {
      const res = await fetch("/api/learn", { cache: "no-store" });
      const data = await res.json();
      setCards(data);
    };
    fetchCards();
  }, []);

  return (
    <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
      {cards.map((card) => (
        <article key={card.id} className="rounded-2xl border p-4 shadow-sm">
          <div className="relative aspect-[4/3] mb-3 overflow-hidden rounded-xl bg-gray-100">
            <img src={card.imgUrl} alt={`${card.ja} / ${card.ko}`} className="object-cover" />
          </div>
          <h2 className="text-lg font-semibold">
            {card.ja} <span className="text-gray-500">/ {card.ko}</span>
          </h2>
          <div className="mt-2 flex flex-wrap gap-2">
            {card.tags.map((t) => (
              <span key={t} className="text-xs rounded-full border px-2 py-1">
                {t}
              </span>
            ))}
          </div>
          <div className="mt-3">
            <span
              className={`text-xs px-2 py-1 rounded ${
                card.status === "published"
                  ? "bg-green-100 text-green-700"
                  : "bg-yellow-100 text-yellow-700"
              }`}
            >
              {card.status}
            </span>
          </div>
        </article>
      ))}
    </div>
  );
}

実際の表示

単語カード一覧の表示例


悩んだこと / 学んだこと

  • useEffect 内で API を呼ぶと、開発モードでは複数回実行されてしまう
    → 原因は React StrictMode。開発時のみ無効化。

  • 取得データをそのまま扱うと複雑になる
    DTO に加工し、UI 側では必要なフィールド(id, ja, ko, imgUrl, tags, status)のみ利用することでコードをシンプルに扱える


次回やること

  • タグ別検索・フィルタリングの実装
  • UI デザインの再調整(ボタンの色・一覧性改善)
  • 新しい単語を保存しようとするとセッションエラー発生 → ログイン中でもセッション満了になるケースを調査・解決

Discussion