Script Valley
Next.js: Full-Stack React Applications
API Routes and Server ActionsLesson 3.3

How to handle form state and errors with useActionState in Next.js

useActionState hook, pending state, validation errors, optimistic updates, useOptimistic, form feedback, progressive enhancement

The Problem with Plain Server Actions

A plain Server Action gives no feedback to the user during submission. useActionState (React 19 / Next.js 15) wraps an action to expose its return value and pending state in the Client Component.

'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions/posts'

const initialState = { error: null, success: false }

export default function NewPostForm() {
  const [state, formAction, isPending] = useActionState(createPost, initialState)

  return (
    <form action={formAction}>
      <input name="title" type="text" />
      {state.error && <p className="text-red-500">{state.error}</p>}
      <button disabled={isPending}>
        {isPending ? 'Publishing...' : 'Publish'}
      </button>
    </form>
  )
}

The Action Must Return State

'use server'
export async function createPost(
  prevState: { error: string | null; success: boolean },
  formData: FormData
) {
  const title = formData.get('title') as string
  if (!title) return { error: 'Title is required', success: false }

  await db.post.create({ data: { title } })
  revalidatePath('/blog')
  return { error: null, success: true }
}

The action now takes prevState as its first argument and returns the new state. useActionState handles the wiring. This pattern gives you validation feedback, loading indicators, and success states with zero manual fetch code.

Up next

How to validate and sanitize data in Next.js Server Actions

Sign in to track progress