React useFormState and useFormStatus

Sanjeev SharmaSanjeev Sharma
2 min read

Advertisement

React useFormState and useFormStatus

These hooks streamline form handling and submission states without complex state management.

useFormStatus

Track form submission state:

'use client'

import { useFormStatus } from 'react-dom'

function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button disabled={pending} type="submit">
      {pending ? 'Saving...' : 'Save'}
    </button>
  )
}

export function ContactForm() {
  return (
    <form action={submitForm}>
      <input name="name" required />
      <textarea name="message" required />
      <SubmitButton />
    </form>
  )
}

useFormState

Manage form submissions:

'use client'

import { useFormState } from 'react-dom'
import { submitForm } from '@/app/actions'

export function MyForm() {
  const [state, formAction] = useFormState(submitForm, null)

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      <input name="message" required />

      {state?.error && <p className="text-red-600">{state.error}</p>}
      {state?.success && <p className="text-green-600">Sent!</p>}

      <button type="submit">Send</button>
    </form>
  )
}

Server action:

// app/actions.ts
'use server'

export async function submitForm(previousState, formData) {
  try {
    const email = formData.get('email')
    const message = formData.get('message')

    // Validation
    if (!email || !message) {
      return { error: 'All fields required' }
    }

    // Process
    await sendEmail(email, message)

    return { success: true }
  } catch (error) {
    return { error: 'Failed to send' }
  }
}

Real-World: Comment Form

'use client'

import { useFormState, useFormStatus } from 'react-dom'
import { addComment } from '@/app/actions'

function CommentSubmit() {
  const { pending } = useFormStatus()

  return (
    <button disabled={pending} type="submit">
      {pending ? 'Posting...' : 'Post Comment'}
    </button>
  )
}

export function CommentForm({ postId }) {
  const [state, formAction] = useFormState(
    (prev, data) => addComment(postId, data),
    null
  )

  return (
    <form action={formAction} className="space-y-4">
      <textarea
        name="content"
        placeholder="Your comment..."
        required
        className="w-full border p-2"
      />

      {state?.error && (
        <p className="text-red-600">{state.error}</p>
      )}

      {state?.success && (
        <p className="text-green-600">Comment posted!</p>
      )}

      <CommentSubmit />
    </form>
  )
}

FAQ

Q: Can I use these hooks outside of forms? A: useFormStatus must be in a descendant of form. useFormState works anywhere.

Q: How do I handle validation errors per field? A: Return field errors in state object and display conditionally.

Q: Do these hooks work with client-side validation? A: Yes, validate before calling the server action.


These hooks make form handling simple and reliable.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro