Using React Hook Form with Next.js Server Actions

Combine React Hook Form with a Next.js Server Action: register fields for client-side validation and pass the action to the form component.

6 min read

React Hook Form with Next.js Server Actions splits the work: the library manages field registration and client-side validation, while the Server Action runs the server write. You pass the action to React Hook Form's Form component, which validates first and then calls it.

You reach for this combination when a form has enough fields that hand-wiring controlled state becomes noisy. The library keeps the client side tidy, and the action keeps the server write in one place.

Install the library first, then create the Server Action the form will submit to.

bashbash
npm install react-hook-form

The install adds the react-hook-form package, and the useForm hook plus the Form component come from it. Passing a function to the Form component's action prop needs version 7.84 or later, so check your lockfile if the action never runs. Next, create the Server Action that reads the submitted fields and writes them on the server.

typescripttypescript
// app/actions.ts
'use server'
import { db } from '@/lib/db'
 
export async function signUp(formData: FormData) {
  const name = String(formData.get('name') ?? '')
  if (name.length < 2) return
  await db.user.create({ data: { name } })
}

The action stays a plain Server Action. React Hook Form does not change how the server side works, only how the client registers and validates the fields, so the same auth and validation rules apply here as in any other action.

Now build the form. Save this as app/signup-form.tsx. React Hook Form registers each input with its rules, and the Form component passes the action to the underlying form element.

App.tsxApp.tsx
'use client'
import { useForm, Form } from 'react-hook-form'
import { signUp } from './actions'
export function SignupForm() {
  const { control, register, formState: { errors } } = useForm<{ name: string }>()
  return <Form action={signUp} control={control}>
    <label>Name <input {...register('name', { required: 'Name is required' })} /></label>
    {errors.name && <p role="alert">{errors.name.message}</p>}
    <button type="submit">Sign up</button>
  </Form>
}

The register call wires the input and its rule, and errors.name shows the message when the rule fails. The component is a Client Component because both hooks run in the browser.

Give useForm a type argument for your fields. Without it the message is a wider union than a string, and rendering it directly is a TypeScript error rather than a runtime one.

Client validation runs first

React Hook Form checks the registered rules before the form submits. When name is empty, the message appears and the action never runs. That is the client-side convenience layer.

It does not replace server-side checks. The action is reachable over HTTP, so validate inside it the same way a plain Server Action form would. Keep the client rules short and put the authoritative rules in a schema or directly in the action.

Registering a field also gives React Hook Form the ref it needs for focus management, so a failed rule can move focus to the first invalid input automatically.

Server errors come back separately

The action's return value does not flow into the register errors automatically. Read it with useActionState and render it next to the field, or use React Hook Form's setError to merge server messages into the same error state.

Merging them into the same errors object keeps the UI consistent, because every message then renders through one path instead of two.

One difference is worth knowing. The FormData your action receives is rebuilt by React Hook Form from the values it validated, not lifted straight off the DOM form, so an input that was never registered does not reach the server.

This form also needs JavaScript. React Hook Form validates in a submit handler and calls the action itself, so unlike a plain Server Action form it does not submit before hydration. Use a plain form when working without JavaScript matters more than client-side validation.

For the visual half, see displaying field-level validation errors from a Server Action, and building forms in Next.js with Server Actions for the base pattern.

Rune AI

Rune AI

Key Insights

  • Install react-hook-form and import useForm and Form.
  • Register each input with its validation rules.
  • Pass the Server Action to the Form action prop.
  • Show errors from formState.errors.
  • Validate again inside the action on the server.
RunePowered by Rune AI

Frequently Asked Questions

What does React Hook Form handle here?

Field registration and client-side validation. The Server Action still performs the server write and the authoritative validation.

Can the form component take a Server Action?

Yes. Since React Hook Form 7.84, the Form component's action prop accepts a function that receives the submitted FormData.

Do I still validate on the server?

Yes. Client-side rules are a convenience. The action is reachable over HTTP, so validate inside it too.

Conclusion

React Hook Form and Server Actions split the work: the library registers fields and validates on the client, while the action runs the server write. Pass the action to the Form component and keep server-side validation as the real guard.