DEV Community

Mattia Malonni
Mattia Malonni

Posted on

Supercharge Your Next.js Forms with next-form-action and useActionState

Supercharge Your Next.js Forms with next-form-action and useActionState

React 19 and Next.js 15 are bringing radical changes to how we build modern web apps, especially with Server Actions and the new useActionState hook. But working with forms can still feel verbose, repetitive, and error-prone. That's why I built next-form-action: a lightweight, type-safe utility to streamline form handling in full-stack React apps.

In this post, I'll show you how next-form-action makes working with forms using useActionState dramatically simpler and more scalable.


The Problem: Boilerplate and Scattered Logic

Here's what a typical login form looks like using useActionState without any helper library:

'use client'

import { useActionState } from 'react'
import { redirect } from 'next/navigation'

async function login(prevState, formData) {
  const email = formData.get('email') as string
  const password = formData.get('password') as string

  if (!email || !password) {
    return { success: false, message: 'Email and password are required' }
  }

  try {
    const user = await authenticate(email, password)
    redirect('/dashboard')
  } catch {
    return { success: false, message: 'Invalid credentials' }
  }
}

export default function LoginForm() {
  const [state, formAction, isPending] = useActionState(login, { success: true, message: '' })

  return (
    <form action={formAction} className="space-y-4">
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      {state.message && <p className="text-red-500">{state.message}</p>}
      <button disabled={isPending}>{isPending ? 'Logging in…' : 'Login'}</button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

It's not bad, but:

  • You have to manually manage state shape
  • You handle errors and redirects manually
  • You miss lifecycle events like onSubmit or onSuccess
  • Reusability and DX could be better

The Solution: next-form-action

With next-form-action, you encapsulate the logic cleanly into an action + hook pair. Here's the same login form, refactored using the library:

Step 1: Define Your Action

// actions/login.ts
import { createAction, error, success } from 'next-form-action'

export const loginAction = createAction('login', async (state, formData) => {
  const email = formData.get('email') as string
  const password = formData.get('password') as string

  if (!email || !password) {
    error('Email and password are required')
  }

  try {
    const user = await authenticate(email, password)
    success('Login successful!', { redirect: '/dashboard' })
  } catch {
    error('Invalid credentials')
  }
})
Enter fullscreen mode Exit fullscreen mode

Highlights:

  • Built-in redirect() support via success()
  • Simplified error management using error()

Step 2: Use in the Component

'use client'

import { useAction } from 'next-form-action'
import { loginAction } from './actions/login'

export default function LoginForm() {
  const { Form, FormError, isPending } = useAction(loginAction)

  return (
    <Form className="space-y-4">
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <FormError className="text-red-500" />
      <button type="submit" disabled={isPending}>
        {isPending ? 'Logging in…' : 'Login'}
      </button>
    </Form>
  )
}
Enter fullscreen mode Exit fullscreen mode

✅ Zero boilerplate. Type safety. Redirects handled. Better separation of concerns.

🧠 Built-in FormError handles dynamic error display based on action response.


Key Features

  • Type-safe form actions with full TS support
  • 🎣 Hooks-based API with built-in Form, FormError, and lifecycle callbacks
  • ⚙️ Built-in error/redirect/refresh support
    • redirect('/path') works natively inside actions
    • notFound() automatically triggers Next.js 404
    • Refresh current page with { refresh: true }
  • 📦 Lightweight & framework-native – no runtime deps, no magic
  • 🧩 Perfect for App Router and Server Actions

Real-World Use Cases

next-form-action shines in real-world apps where:

  • ✅ You want reliable forms with built-in error handling and automatic error display
  • 🔄 You want clean, declarative redirects and refresh behavior after actions
  • 📊 You want to plug into analytics or logging via onFormSubmit / onFormError
  • 📦 You want to reduce boilerplate and unify form structure across your app

Requirements

  • React 19+
  • Next.js 15+ with App Router
  • TypeScript 5+

Try It Now

Install it with your favorite package manager:

npm install next-form-action
Enter fullscreen mode Exit fullscreen mode

Explore the docs and source:


Final Thoughts

Modern full-stack React deserves ergonomic form handling. next-form-action brings the DX you expect from tools like react-hook-form, while embracing the new world of useActionState and Server Actions.

If you're building anything with React 19 or Next.js 15, give it a shot — and say goodbye to fragile forms and verbose logic.


If you liked this, consider starring the repo, or sharing the library with someone working on Next.js forms. ❤️

Top comments (0)