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 ส่วนบทนี้คือกลไก)
Batching: หลาย setState, render เดียว
หัวข้อที่มีชื่อว่า “Batching: หลาย setState, render เดียว”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 สอดคล้องกัน
Updater function เทียบกับ value
หัวข้อที่มีชื่อว่า “Updater function เทียบกับ value”ส่ง value เมื่อ state ถัดไปไม่ได้ขึ้นกับค่าก่อนหน้า ส่ง updater function เมื่อขึ้นกับค่าก่อนหน้า — React รัน updater ตามลำดับกับค่า pending ล่าสุด
setCount(count + 1); // value: uses the snapshot; three of these still add 1 totalsetCount(c => c + 1); // updater: uses the pending value; three of these add 3flowchart 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"]
หลักง่าย ๆ: ถ้าคุณเรียก setter ตัวเดิมมากกว่าหนึ่งครั้งใน handler หรือค่าใหม่คำนวณจากค่าเก่า ให้ใช้ updater form
Lazy initialization
หัวข้อที่มีชื่อว่า “Lazy initialization”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());state ต้อง immutable
หัวข้อที่มีชื่อว่า “state ต้อง immutable”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]); // addsetItems(items.filter(i => i.id !== id)); // removesetItems(items.map(i => i.id === id ? { ...i, done: true } : i)); // updateสำหรับ state ที่ nested ลึก การ spread จะเริ่มยุ่ง — นั่นคือสัญญาณว่าควร flatten รูปทรง, ใช้ library อย่าง Immer, หรือหันไปใช้ useReducer (บทเรียนถัดไป)