
กำลังโหลด...

กำลังโหลด...

มาถึงตรงนี้แปลว่าเก่งมากแล้ว! ด่านสุดท้ายคือรวมทุกอย่างที่เรียนมาทั้งวันเป็น โปรเจคเดียว — ทำเว็บ Blog Viewer ที่มี Layout, Navbar, หน้ารวมโพสต์, หน้า รายละเอียด ดึงข้อมูลจริง พร้อม Loading/Error State ครบ ลองทำเองให้เต็มที่นะ
เป้าหมายของ Mission นี้: รวมทุกอย่างที่เรียนมาทั้งคอร์สเป็น Mini Project เดียว — Blog Viewer ที่ใช้งานได้จริง
flowchart TD
L["Layout: Navbar ทุกหน้า"] --> P["/posts: รายการโพสต์"]
P -->|"คลิกโพสต์"| D["/posts/[id]: รายละเอียด"]
D -->|"ปุ่มย้อนกลับ"| P
P --> S["Loading / Error / Empty State"]
D --> S
/posts) — ดึงข้อมูลจริงมาแสดงเป็นรายการ (จาก Chapter 4)/posts/[id]) — คลิกจากหน้ารวมแล้วมาดูโพสต์เดียว (จาก Chapter 5)ใช้ https://jsonplaceholder.typicode.com/posts เหมือนที่ฝึกมาใน Chapter 4-5 ได้เลย — หรือ
ถ้าอยากลองอย่างอื่น ก็มี https://jsonplaceholder.typicode.com/users (รายชื่อผู้ใช้) ให้ลองทำ
เป็น "Product List" แบบ mock ก็ได้เหมือนกัน โครงสร้างเหมือนกันหมด
app/posts/page.tsx,
app/posts/[id]/page.tsx) — เอามาต่อยอด ไม่ต้องเริ่มใหม่จากศูนย์app/layout.tsx ว่ามีลิงก์ไปหน้า /posts ด้วยหรือยัง<Link href="/posts">)หน้า Detail ต้องมีทั้ง Loading/Error State (ต้องใช้ "use client") และอ่าน id จาก URL —
สองอย่างนี้มาเจอกันตรง use(params) อย่าเผลอเขียน params.id ตรงๆ
// app/posts/[id]/page.tsx
"use client";
import { use, useState, useEffect } from "react";
import Link from "next/link";
type Post = { id: number; title: string; body: string };
export default function PostDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = use(params); // Client Component แกะ params ด้วย use() แทน await
const [post, setPost] = useState<Post | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
async function loadPost() {
setLoading(true);
setError("");
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${id}`,
);
if (!response.ok) throw new Error("ไม่พบโพสต์นี้");
setPost(await response.json());
} catch (err) {
setError("โหลดข้อมูลไม่สำเร็จ");
} finally {
setLoading(false);
}
}
loadPost();
}, [id]);
if (loading) return <p className="p-8">กำลังโหลด...</p>;
if (error) return <p className="p-8 text-red-600">{error}</p>;
if (!post) return <p className="p-8">ไม่พบโพสต์</p>;
return (
<article className="p-8">
<Link href="/posts" className="text-blue-600">
← กลับไปหน้ารวม
</Link>
<h1 className="mt-4 text-2xl font-bold">{post.title}</h1>
<p className="mt-4 text-neutral-600">{post.body}</p>
</article>
);
}
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
fetch ไปที่ /posts/undefined |
อ่าน params.id ตรงๆ ในไฟล์ที่มี "use client" |
แกะด้วย const { id } = use(params) ก่อน |
Error ว่าเขียน async component ใน Client ไม่ได้ |
ใส่ async คู่กับ "use client" |
เอา async ออก แล้วใช้ use() แทน await |
| สลับโพสต์แล้วข้อมูลไม่เปลี่ยน | ลืมใส่ id ใน dependency array |
useEffect(..., [id]) |
ไม่ต้องเริ่มใหม่จากศูนย์เด็ดขาด เพราะจะทำไม่ทัน — ต่อยอดจากของเดิมที่ทำมาทั้งวัน แล้วเติมส่วนที่ ยังขาดให้ครบ