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

useState & Updates

useState คืนค่าเป็นคู่: value ปัจจุบันสำหรับ render นี้ และ setter ที่ schedule ให้ re-render ด้วย value ใหม่

const [count, setCount] = useState(0);
// count → the value for THIS render (a snapshot)
// setCount(next) → schedule a re-render where count is `next`

การเรียก setter ไม่เปลี่ยน count ทันที — แต่เป็นการขอให้ React render อีกครั้ง ใน render ถัดไป useState(0) จะคืน value ที่อัปเดตแล้ว (บทเรียน “state เป็น snapshot” ใน Foundations คือ mental model ส่วนบทนี้คือกลไก)

React batch การอัปเดต state ที่เกิดใน event เดียวกัน หลายการเรียก setter ใน handler เดียวจะได้ re-render ครั้งเดียว ไม่ใช่ครั้งต่อการเรียก

function handleClick() {
setCount(c => c + 1);
setFlag(true);
setName("Ada");
// → ONE re-render, with all three updates applied. React batches them.
}

ตั้งแต่ React 18 การ batch นี้ครอบคลุมการอัปเดตใน promise, timeout และ native event handler ด้วย — batch เกิดอัตโนมัติทุกที่ render น้อยลง UI สอดคล้องกัน

ส่ง value เมื่อ state ถัดไปไม่ได้ขึ้นกับค่าก่อนหน้า ส่ง updater function เมื่อขึ้นกับค่าก่อนหน้า — React รัน updater ตามลำดับกับค่า pending ล่าสุด

setCount(count + 1); // value: uses the snapshot; three of these still add 1 total
setCount(c => c + 1); // updater: uses the pending value; three of these add 3
flowchart LR
  start["pending = 0"] --> u1["c => c + 1
→ 1"]
  u1 --> u2["c => c + 1
→ 2"]
  u2 --> u3["c => c + 1
→ 3"]
  u3 --> done["next render: count = 3"]
updater function ทำงานกับค่าที่เปลี่ยนไปเรื่อย ๆ

หลักง่าย ๆ: ถ้าคุณเรียก setter ตัวเดิมมากกว่าหนึ่งครั้งใน handler หรือค่าใหม่คำนวณจากค่าเก่า ให้ใช้ updater form

argument ของ useState ถูกใช้แค่ใน render แรก เท่านั้น แต่ React ยัง evaluate argument นั้นทุก render (แล้วทิ้ง) ถ้าการคำนวณ initial value แพง ให้ส่ง function — React จะเรียกแค่ครั้งเดียว

// ❌ createInitialTodos() runs on EVERY render (result discarded after the first).
const [todos, setTodos] = useState(createInitialTodos());
// ✅ Lazy: the function runs only on the first render.
const [todos, setTodos] = useState(() => createInitialTodos());

React ตัดสินใจว่าจะ re-render ไหมโดยเทียบ reference การ mutate object หรือ array ในที่เดิมทำให้ reference เดิม React จึงอาจข้ามการอัปเดต — และทำลาย snapshot model สร้าง value ใหม่เสมอ

// ❌ Mutation — same reference, unreliable render.
user.name = "Ada";
setUser(user);
// ✅ New object.
setUser({ ...user, name: "Ada" });
// ✅ New arrays for add / remove / update.
setItems([...items, newItem]); // add
setItems(items.filter(i => i.id !== id)); // remove
setItems(items.map(i => i.id === id ? { ...i, done: true } : i)); // update

สำหรับ state ที่ nested ลึก การ spread จะเริ่มยุ่ง — นั่นคือสัญญาณว่าควร flatten รูปทรง, ใช้ library อย่าง Immer, หรือหันไปใช้ useReducer (บทเรียนถัดไป)

การเรียก setter ของ useState ทำอะไร?
เมื่อไรควรใช้ updater form `setX(x => ...)`?
ทำไมต้องส่ง function ให้ useState (`useState(() => expensive())`)?
ทำไมต้องอัปเดต object/array state แบบ immutable?