v4·hookform-action-standalone

Standalone Adapter

Use hookform-action in any React app — Vite, Remix, Astro, or plain SPAs. No Next.js or Server Actions required.

Installation

npm install hookform-action-standalone react-hook-form zod
# or
pnpm add hookform-action-standalone react-hook-form zod

hookform-action-standalone depends on hookform-action-core (core) as a regular dependency — it's installed automatically.

Basic Usage

Instead of passing a Server Action as the first argument, pass an options object with a submit function:

import { useActionForm } from 'hookform-action-standalone'
import { z } from 'zod'

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Minimum 8 characters'),
})

export function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isPending, isSubmitSuccessful },
  } = useActionForm({
    submit: async (data) => {
      const res = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(data),
        headers: { 'Content-Type': 'application/json' },
      })
      return res.json()
    },
    schema,
    validationMode: 'onChange',
    defaultValues: { email: '', password: '' },
  })

  if (isSubmitSuccessful) {
    return <p className="text-green-500">✓ Logged in!</p>
  }

  return (
    <form onSubmit={handleSubmit()}>
      <input {...register('email')} placeholder="Email" />
      {errors.email && <span>{errors.email.message}</span>}

      <input {...register('password')} type="password" placeholder="Password" />
      {errors.password && <span>{errors.password.message}</span>}

      <button disabled={isPending}>
        {isPending ? 'Signing in...' : 'Sign In'}
      </button>
    </form>
  )
}

Next.js vs Standalone

FeatureNext.js AdapterStandalone Adapter
Importhookform-actionhookform-action-standalone
SignatureuseActionForm(action, options)useActionForm({ submit, ...options })
formAction✅ Provided❌ Not applicable
FormDataAuto-convertsNot needed
Error mapping✅ Identical✅ Identical
Optimistic UI✅ Identical✅ Identical
Persistence✅ Identical✅ Identical
Client validation✅ Identical✅ Identical
DevTools✅ Compatible✅ Compatible

Vite Setup

# Create a Vite + React + TypeScript project
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install hookform-action-standalone react-hook-form zod
npm install hookform-action-devtools   # optional

No additional Vite configuration needed. The library ships ESM + CJS and works out of the box.

Optimistic UI Example

import { useActionForm } from 'hookform-action-standalone'

function EditTodo({ todo }: { todo: Todo }) {
  const { register, handleSubmit, optimistic } = useActionForm({
    submit: async (data) => {
      const res = await fetch(`/api/todos/${todo.id}`, {
        method: 'PATCH',
        body: JSON.stringify(data),
        headers: { 'Content-Type': 'application/json' },
      })
      return res.json()
    },
    defaultValues: { title: todo.title },
    optimisticKey: `todo-${todo.id}`,
    optimisticInitial: todo,
    optimisticData: (current, values) => ({ ...current, ...values }),
  })

  return (
    <form onSubmit={handleSubmit()}>
      <h2>{optimistic?.data?.title}</h2>
      <input {...register('title')} />
      <button type="submit">Save</button>
    </form>
  )
}

Bundle Size

PackageESM (minified)Peer deps
hookform-action-core (core)~4 KBreact, react-hook-form, zod
hookform-action-standalone~1 KB (adapter only)react, react-hook-form, zod
hookform-action-devtools~3 KBreact, react-hook-form

All packages are tree-shakeable (sideEffects: false). DevTools is only included when imported — typically behind a process.env.NODE_ENV guard.

MIT License · Built with ♥ for the React community