Actions & useActionState
pattern ที่ React 19 ลบทิ้ง
หัวข้อที่มีชื่อว่า “pattern ที่ React 19 ลบทิ้ง”ก่อนมี Actions การ submit form หมายถึงต้อง wire state สามชิ้นด้วยมือ และต้องจำอัปเดตทุกตัว:
// The old way — lots of manual bookkeeping.function ChangeName() { const [name, setName] = useState(''); const [isPending, setIsPending] = useState(false); const [error, setError] = useState(null);
async function handleSubmit(e) { e.preventDefault(); setIsPending(true); setError(null); try { await updateName(name); } catch (err) { setError(err.message); } finally { setIsPending(false); // must not forget this } } // ...form wired to handleSubmit}async form ทุกอันในแอปทำซ้ำแบบนี้: loading flag, error slot, try/catch/finally ทั้งหมดนี้คือ boilerplate และถ้าลืม finally spinner จะค้างตลอดกาล
Action: async function ที่ส่งให้ form
หัวข้อที่มีชื่อว่า “Action: async function ที่ส่งให้ form”Action ก็แค่ async function ที่คุณส่งให้ <form> ผ่าน prop action React เรียก function นี้ตอน submit, ส่ง FormData ให้ และจัดการ pending state กับการ reset form ให้:
<form action={async (formData) => { await updateName(formData.get('name'));}}> <input name="name" /> <button type="submit">Update</button></form>ไม่ต้อง onSubmit, ไม่ต้อง preventDefault, ไม่ต้องประกอบ FormData เอง แต่คุณยังต้องการ state ของ pending และ error เพื่อแสดงให้ user เห็นว่าเกิดอะไรขึ้น — นั่นคือสิ่งที่ useActionState เพิ่มเข้ามา
useActionState: pending, error และ result ใน call เดียว
หัวข้อที่มีชื่อว่า “useActionState: pending, error และ result ใน call เดียว”useActionState(action, initialState) ห่อ action ของคุณและคืน tuple สามตัว: state ปัจจุบัน, action ที่ห่อแล้วสำหรับส่งให้ form และ boolean pending
import { useActionState } from 'react';
function ChangeName() { const [error, submitAction, isPending] = useActionState( async (previousState, formData) => { const error = await updateName(formData.get('name')); if (error) { return error; // returned value becomes the new state } redirect('/profile'); return null; // success: clear the error }, null, // initial state );
return ( <form action={submitAction}> <input type="text" name="name" /> <button type="submit" disabled={isPending}>Update</button> {error && <p>{error}</p>} </form> );}อ่าน shape ให้ดี ๆ — นี่คือส่วนที่คนมักทำผิด:
- action รับ
(previousState, formData)และ return state ถัดไป useActionStateคืน[state, formAction, isPending]—stateคือค่าที่ action คุณ return ล่าสุด,formActionคือสิ่งที่ส่งให้<form action={...}>และisPendingถูกจัดการให้
sequenceDiagram participant U as User participant F as form action participant A as your async action U->>F: submit F->>F: isPending = true F->>A: action(prevState, formData) A-->>F: returns next state F->>F: isPending = false, state updated F->>U: re-render with new state
ทั้งการเต้นสามจังหวะ — loading flag, error slot, try/catch/finally — ยุบเหลือ hook เดียวที่ลืม reset spinner ไม่ได้