ข้ามไปยังเนื้อหา

use() & Data Fetching

use() เป็น API ของ React 19 ที่ อ่านค่าของ resource ระหว่าง render — ที่สำคัญที่สุดคือ promise หรือ context เมื่อคุณส่ง promise เข้าไป component จะ suspend ระหว่างที่ promise ยัง pending (แสดง Suspense fallback ที่ใกล้ที่สุด) และคืนค่าที่ resolve แล้วเมื่อ promise settle

import { use, Suspense } from "react";
function Message({ messagePromise }) {
const text = use(messagePromise); // suspend จนกว่า promise จะ resolve
return <p>{text}</p>;
}
function Container({ messagePromise }) {
return (
<Suspense fallback={<p>Loading…</p>}>
<Message messagePromise={messagePromise} />
</Suspense>
);
}

use() แหกกฎข้อหนึ่งที่คุณเรียนกับ hook อื่น: คุณ เรียกแบบมีเงื่อนไขได้ — ใน if หรือใน loop ที่เป็นแบบนั้นได้เพราะไม่ได้ผูกกับ hook slot ตายตัวแบบ useState/useEffect

use() ไม่ เริ่ม fetch และไม่ cache อะไรเลย แค่อ่าน promise ที่คุณให้ — ดังนั้น promise นั้น ต้องเสถียรข้าม render การสร้าง promise ใน body ของ component หมายถึง promise ใหม่ทุก render ซึ่งจะ suspend ตลอดกาล (หรือ refetch ไม่หยุด)

// ❌ promise ใหม่ทุก render — refetch / ไม่ settle
function Bad({ id }) {
const data = use(fetch(`/api/${id}`).then(r => r.json()));
// ...
}
// ✅ promise ถูกสร้างข้างนอกและส่งเข้ามา (cache โดย caller/framework)
function Good({ dataPromise }) {
const data = use(dataPromise);
// ...
}

นี่คือเหตุผลที่ use() ดิบ ๆ มักจับคู่กับ cache เสมอ — ไม่ว่าจะเป็น data layer ของ framework หรือ Map cache ที่ทำเอง — ที่คืน promise instance เดิม สำหรับ request เดิม

pattern ที่ use() เปิดทางให้คือ render-as-you-fetch: เริ่ม request ก่อนหรือขณะ ที่คุณ render (เช่นใน route loader หรือตอน hover) ส่ง promise ลงไป แล้วปล่อยให้ component อ่าน promise นั้นผ่าน use() ใน Suspense boundary เทียบสามแนวทาง:

flowchart TB
  subgraph old["fetch-on-render (useEffect)"]
    r1["render"] --> e1["effect รัน"] --> f1["แล้ว fetch ค่อยเริ่ม"] --> r2["re-render พร้อม data"]
  end
  subgraph new["render-as-you-fetch (use + Suspense)"]
    start["เริ่ม fetch ก่อน
(loader / event)"] --> pass["ส่ง promise ลงไป"]
    pass --> read["use(promise) suspend
→ Suspense fallback → data"]
  end
fetch-on-render vs render-as-you-fetch

use() ยังอ่าน context ได้ด้วย และต่างจาก useContext ตรงที่เรียกแบบมีเงื่อนไขได้ — สะดวกสำหรับอ่าน context ใน branch

นี่คือส่วนที่ต้องซื่อสัตย์ use() ดิบ ๆ + Suspense + promise cache ที่เขียนเองนั้นทรงพลังแต่ระดับต่ำ ในแอปจริงคุณเกือบทุกครั้งจะได้ render-as-you-fetch ผ่าน framework (router/loader ที่ fetch ต่อ route และให้ cache) หรือ data library อย่าง TanStack Query (ที่จัดการ caching, revalidation และ Suspense integration ให้) เรียน use() เพื่อเข้าใจกลไก; ใช้ library เพื่อ ship จริง

เกิดอะไรขึ้นเมื่อคุณส่ง promise ที่ pending เข้า `use()`?
`use()` ต่างจาก hook อย่าง `useState` อย่างไร?
ทำไม promise ที่ส่งเข้า `use()` ต้องถูก cache / เสถียรข้าม render?
ในทางปฏิบัติ แอปส่วนใหญ่ทำ render-as-you-fetch อย่างไร?