How do I handle Zod validation errors with useActionState in Next.js 15?
#86447
-
|
I am upgrading to Next.js 15 (React 19 RC) and I see that Previously I just returned an object, but I'm confused about the initial state and type safety with the new hook. Does anyone have a clean pattern for this? |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
|
Hi there! 👋 This is a great question! The transition from Here is a robust pattern using Zod for validation and TypeScript for type-safe error handling. Step 1: Define your State Type & ActionFirst, create your action. Note that we return a // actions.ts
'use server'
import { z } from 'zod'
// 1. Define the Zod Schema
const subscribeSchema = z.object({
email: z.string().email({ message: "Please enter a valid email address." }),
})
// 2. Define the State interface
export type FormState = {
errors?: {
email?: string[]
}
message?: string | null
}
// 3. Create the Server Action
export async function subscribe(prevState: FormState, formData: FormData): Promise<FormState> {
// Extract data
const email = formData.get('email')
// Validate
const validatedFields = subscribeSchema.safeParse({
email,
})
// Handle Failure
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Subscribe.',
}
}
// Handle Success (e.g., save to DB)
// await db.subscribe(validatedFields.data.email)
return {
message: 'You have been subscribed successfully!',
}
}Step 2: Connect it to the Client ComponentNow use the // components/SubscribeForm.tsx
'use client'
import { useActionState } from 'react'
import { subscribe, FormState } from '@/app/actions'
const initialState: FormState = {
message: null,
errors: {},
}
export function SubscribeForm() {
// Connect the action to the state
const [state, formAction, isPending] = useActionState(subscribe, initialState)
return (
<form action={formAction} className="flex flex-col gap-4">
<div>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
className="border p-2 rounded"
/>
{/* Display Error for Email */}
{state.errors?.email && (
<p className="text-red-500 text-sm">{state.errors.email[0]}</p>
)}
</div>
<button type="submit" disabled={isPending}>
{isPending ? 'Submitting...' : 'Subscribe'}
</button>
{/* Display Global Message */}
{state.message && (
<p className="text-gray-700 font-medium">{state.message}</p>
)}
</form>
)
}
Happy coding! 🚀 |
Beta Was this translation helpful? Give feedback.
Hi there! 👋
This is a great question! The transition from
useFormStatetouseActionStateis one of the most common friction points in the Next.js 15 upgrade, but the new API is actually cleaner once you get the boilerplate down.Here is a robust pattern using Zod for validation and TypeScript for type-safe error handling.
Step 1: Define your State Type & Action
First, create your action. Note that we return a
Stateobject that includes botherrorsand amessage.