Mutations, APIs and security · Lesson 9 of 16
Server Actions
Server functions called from forms, validation, and useActionState.
A Server Action is an async function marked "use server" that runs only on the server, yet can be called from a form or button on the client like a normal function. Next.js makes the request behind the scenes, so you don't need an API route for every form.
// app/settings/actions.ts
"use server"
import { revalidatePath } from "next/cache"
import { createClient } from "@/utils/supabase/server"
export async function saveBio(previous: { error?: string }, formData: FormData) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return { error: "Please sign in." } // 1. who is calling?
const bio = String(formData.get("bio") ?? "").trim()
if (bio.length > 160) return { error: "Bio must be 160 characters or less." } // 2. validate
const { error } = await supabase.from("profiles").update({ bio }).eq("id", user.id)
if (error) return { error: "Couldn't save. Try again." }
revalidatePath("/settings") // 3. refresh the page data
return {}
}// app/settings/bio-form.tsx
"use client"
import { useActionState } from "react"
import { saveBio } from "./actions"
export function BioForm({ bio }: { bio: string }) {
const [state, formAction, pending] = useActionState(saveBio, {})
return (
<form action={formAction}>
<textarea name="bio" defaultValue={bio} maxLength={160} />
<button disabled={pending}>{pending ? "Saving…" : "Save"}</button>
{state.error && <p role="alert">{state.error}</p>}
</form>
)
}The form submits
Next.js POSTs the FormData to the server
saveBio: check user → validate → save → revalidate
Response: new state + the page with fresh data
Spot the bug
This action has a serious vulnerability. Which line?
Exercise
Write validateCourse(formData) returning an array of errors: title is required when the title is empty, and price must be 0 or more when the price is negative or not a number. Print the result for both forms (one error per line, or ok when there are none).
ok title is required price must be 0 or more
function validateCourse(formData) {
const errors = []
return errors
}
const good = new FormData()
good.set("title", "React")
good.set("price", "600")
const bad = new FormData()
bad.set("title", " ")
bad.set("price", "-5")
for (const form of [good, bad]) {
const errors = validateCourse(form)
console.log(errors.length ? errors.join("\n") : "ok")
}Learn more from
Played 0 of 2 games
Sign in to save your progress and earn XP.