Form Actions
ใน React การ submit form มักหมายถึงการดัก onSubmit event, เรียก e.preventDefault(), และส่ง data ไปยัง API ด้วย fetch เอง SvelteKit มีแนวทางที่ต่างออกไปด้วย form actions — function ฝั่ง server ที่ผูกติดกับ +page.server.js โดยตรง ทำให้ form ทำงานได้โดยไม่ต้องใช้ JavaScript เลย แล้วค่อย enhance ด้วย use:enhance เพื่อ UX แบบ SPA
Form action พื้นฐาน
หัวข้อที่มีชื่อว่า “Form action พื้นฐาน”// React + API Route// app/api/contact/route.tsexport async function POST(req) { const data = await req.formData(); const name = data.get('name'); const email = data.get('email'); await saveContact({ name, email }); return Response.json({ ok: true });}
// components/ContactForm.tsxfunction ContactForm() { const [status, setStatus] = React.useState('');
async function handleSubmit(e) { e.preventDefault(); const form = new FormData(e.target); const res = await fetch('/api/contact', { method: 'POST', body: form, }); if (res.ok) setStatus('Sent!'); }
return ( <form onSubmit={handleSubmit}> <input name="name" placeholder="Name" required /> <input name="email" type="email" placeholder="Email" required /> <button type="submit">Send</button> {status && <p>{status}</p>} </form> );}<!-- src/routes/contact/+page.server.js -->export const actions = { default: async ({ request }) => { const data = await request.formData(); const name = data.get('name'); const email = data.get('email'); await saveContact({ name, email }); return { success: true }; },};
<!-- src/routes/contact/+page.svelte --><script> let { form } = $props();</script>
<form method="POST"> <input name="name" placeholder="Name" required /> <input name="email" type="email" placeholder="Email" required /> <button type="submit">Send</button> {#if form?.success} <p>Sent!</p> {/if}</form>ข้อสังเกตสำคัญ: ไม่มี onSubmit handler, ไม่มี fetch, ไม่มี e.preventDefault() ใน Svelte — form ชี้ไปที่ server action ด้วย method="POST" และ server return ข้อมูลกลับมาผ่าน form prop โดยอัตโนมัติ
Named actions
หัวข้อที่มีชื่อว่า “Named actions”หน้าเดียวสามารถมีหลาย action ได้ เช่น form สำหรับ login และ register อยู่บนหน้าเดียวกัน
export const actions = { login: async ({ request }) => { const data = await request.formData(); // ตรวจสอบ credentials return { success: true }; }, register: async ({ request }) => { const data = await request.formData(); // สร้าง account ใหม่ return { success: true }; },};<form method="POST" action="?/login"> <input name="email" type="email" /> <input name="password" type="password" /> <button>Login</button></form>
<form method="POST" action="?/register"> <input name="email" type="email" /> <input name="password" type="password" /> <button>Register</button></form>Progressive enhancement ด้วย use:enhance
หัวข้อที่มีชื่อว่า “Progressive enhancement ด้วย use:enhance”Form actions ทำงานได้แม้ไม่มี JavaScript (เหมือน HTML form ธรรมดา) เพิ่ม use:enhance เพื่ออัปเกรดเป็น client-side experience โดยไม่ต้องเขียน fetch logic เอง
// React: ต้องจัดการทุกอย่างเองfunction LoginForm() { const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState('');
async function handleSubmit(e) { e.preventDefault(); setLoading(true); setError(''); try { const res = await fetch('/api/login', { method: 'POST', body: new FormData(e.target), }); const json = await res.json(); if (!json.ok) setError(json.message); } catch { setError('Network error'); } finally { setLoading(false); } }
return ( <form onSubmit={handleSubmit}> <input name="email" type="email" /> <input name="password" type="password" /> <button disabled={loading}> {loading ? 'Logging in...' : 'Login'} </button> {error && <p className="error">{error}</p>} </form> );}<!-- +page.server.js -->export const actions = { default: async ({ request, cookies }) => { const data = await request.formData(); const user = await authenticate( data.get('email'), data.get('password') ); if (!user) { return fail(400, { message: 'Invalid credentials' }); } cookies.set('session', user.token, { path: '/' }); redirect(303, '/dashboard'); },};
<!-- +page.svelte --><script> import { enhance } from '$app/forms'; let { form } = $props(); let loading = $state(false);</script>
<form method="POST" use:enhance={() => { loading = true; return async ({ update }) => { await update(); loading = false; };}}> <input name="email" type="email" /> <input name="password" type="password" /> <button disabled={loading}> {loading ? 'Logging in...' : 'Login'} </button> {#if form?.message} <p class="error">{form.message}</p> {/if}</form>use:enhance จัดการ fetch, อัปเดต form prop, และ invalidate data ให้อัตโนมัติ คุณแค่เพิ่ม callback เพื่อจัดการ loading state เท่านั้น
Return ข้อมูลและ validation errors
หัวข้อที่มีชื่อว่า “Return ข้อมูลและ validation errors”ใช้ fail() เพื่อส่ง validation errors กลับมาพร้อม status code ที่เหมาะสม
import { fail } from '@sveltejs/kit';
export const actions = { default: async ({ request }) => { const data = await request.formData(); const email = data.get('email');
if (!email || !email.includes('@')) { return fail(422, { email, errors: { email: 'กรุณากรอก email ที่ถูกต้อง' }, }); }
await subscribe(email); return { success: true }; },};<script> let { form } = $props();</script>
<form method="POST"> <input name="email" type="email" value={form?.email ?? ''} /> {#if form?.errors?.email} <p class="error">{form.errors.email}</p> {/if} <button>Subscribe</button> {#if form?.success} <p>สมัครสำเร็จแล้ว!</p> {/if}</form>SvelteKit จะ repopulate ค่าใน field อัตโนมัติผ่าน form prop เมื่อ validation ล้มเหลว ไม่ต้องเขียน logic นี้เอง