| | |

React Hook Form 20 ๐Ÿ“‹ Server Actions Integration โ€” React 19 and Next.js patterns

โฑ๏ธ Estimated read: 14 min | |

The Server Actions are the React 19 and Next.js mechanism for the mutations that run on the server, and the React Hook Form is the client-side state manager. The two are not the same layer, and the integration is the question of where the validation and the form state live. The Next.js documentation offers two patterns: the client-side validation with the React Hook Form (the zodResolver or the yupResolver) plus the Server Action for the mutation, and the server-side validation with the useActionState hook and the Server Action as the form action. The React Hook Form pattern is the choice when the form needs the rich client-side featuresโ€”the dynamic fields, the watch, the formState subscriptionโ€”and the Server Action pattern is the choice when the form is the simple and the server is the source of truth.

The integration pattern that the Next.js documentation recommends for the React Hook Form is the Client Component that calls the Server Action. The handleSubmit validates the form on the client, and the onSubmit handler calls the Server Action with the typed data. The useTransition is the mechanism that tracks the Server Action’s pending state, and the formState.isSubmitting is the React Hook Form’s own state . The Server Action is the separate function with the 'use server' directive, and the directive is the marker that makes the function callable from the client. The two validationsโ€”the client and the serverโ€”are the defense in depth, and the depth is the reason the Server Action re-validates the input even when the client already did.

Key point: The React Hook Form and the Server Actions are the two separate layers. The useForm with the resolver validates on the client, and the onSubmit handler calls the Server Action with the validated data. The useTransition is the pending-state mechanism for the Server Action, and the formState.isSubmitting is the React Hook Form’s state. The Server Action re-validates the input because the client validation is the untrusted convenience, and the server validation is the security boundary.


Why the integration needs the two validations

The client-validation problem. The React Hook Form validates the form on the client, and the client validation is the UX mechanismโ€”the immediate error, the focus on the field, the disabled submit button. The client validation is not the security mechanism, because the client code can be bypassed, and the Server Action is the public endpoint that any client can call with any payload . The server validation is the mechanism that keeps the invalid data out of the database, and the Server Action is the place where the server validation runs.

The pending-state problem. The Server Action is the async function, and the form needs to show the pending state while the action runs. The useTransition is the mechanism that tracks the action’s pending state, and the isPending is the boolean that the UI reads . The React Hook Form’s formState.isSubmitting is the separate state that tracks the handleSubmit‘s async handler, and the two are the different mechanisms that the integration must coordinate.

The error problem. The Server Action can fail with the validation error, the authorization error, or the unexpected exception. The setError is the mechanism that maps the server error to the React Hook Form’s field, and the mapping is the reason the server error appears in the same place as the client error . The action’s return value is the mechanism that carries the error, and the return value is the reason the client can display it.

The re-validation problem. The Next.js documentation states the rule plainly: “Always re-validate the input on the server, even if the client validation passes” . The re-validation is the mechanism that keeps the malicious client from bypassing the client checks, and the bypass is the reason the Server Action does not trust the payload.

The progressive-enhancement problem. The React Hook Form is the Client Component, and the Client Component requires the JavaScript. The Server Action as the form action is the mechanism that works without the JavaScript, and the useActionState is the pattern that the Next.js documentation recommends for the progressive enhancement . The React Hook Form pattern and the useActionState pattern are the two ends of the spectrum, and the choice is the client features against the progressive enhancement.


a. The React Hook Form and the Server Action

The Client Component calls the useForm with the resolver, and the handleSubmit calls the Server Action. The useTransition is the mechanism that tracks the pending state.

// The Server Action
'use server';

import { z } from 'zod';
import { db } from '@/lib/db';

// The server-side schema is the separate source of truth.
const schema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

export async function createUser(formData: FormData) {
  // The Server Action re-validates the input.
  const parsed = schema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
  });

  if (!parsed.success) {
    // The return value carries the error to the client.
    return { error: 'Invalid input' };
  }

  await db.user.create({ data: parsed.data });
  return { error: null };
}
// The Client Component
'use client';

import { useForm } from 'react-hook-form';
import { useTransition } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { createUser } from '@/app/actions';

// The client schema mirrors the server schema.
const schema = z.object({
  name: z.string().min(2, 'At least 2 characters'),
  email: z.string().email('Invalid email'),
});

type FormValues = z.infer<typeof schema>;

function UserForm() {
  const [isPending, startTransition] = useTransition();
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormValues>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormValues) => {
    // The startTransition tracks the Server Action's pending state.
    startTransition(async () => {
      const formData = new FormData();
      formData.append('name', data.name);
      formData.append('email', data.email);

      // The Server Action is called with the FormData.
      const result = await createUser(formData);

      if (result.error) {
        // The server error is mapped to the form.
        setError('root.server', { message: result.error });
      }
    });
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} disabled={isPending} />
      {errors.name && <p role="alert">{errors.name.message}</p>}
      <input {...register('email')} disabled={isPending} />
      {errors.email && <p role="alert">{errors.email.message}</p>}
      {errors.root?.server && <p role="alert">{errors.root.server.message}</p>}
      <button type="submit" disabled={isPending}>
        {isPending ? 'Savingโ€ฆ' : 'Save'}
      </button>
    </form>
  );
}

The startTransition(async () => { ... }) is the mechanism that tracks the Server Action’s pending state. The isPending is the boolean that the UI reads, and the UI is the reason the user sees the disabled state . The createUser(formData) is the Server Action call, and the formData is the mechanism that carries the values.

The errors.root.server is the mechanism that displays the server-level error. The setError('root.server', { message }) is the mechanism that maps the action’s return value to the form’s root error, and the root error is the reason the general failure is visible .

The two validations are the defense in depth. The client zodResolver(schema) is the immediate feedback, and the server schema.safeParse is the security boundary . The two schemas are the same shape, and the shape is the reason the messages match.


b. The useActionState pattern and the progressive enhancement

The useActionState is the React 19 hook that the Next.js documentation recommends for the Server Action forms. The hook manages the state and the pending flag, and the form works without the JavaScript.

// The Server Action with the useActionState
'use server';

import { z } from 'zod';
import { db } from '@/lib/db';

const schema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

export async function createUser(prevState, formData) {
  const parsed = schema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
  });

  if (!parsed.success) {
    // The return value becomes the state.
    return { error: 'Invalid input' };
  }

  await db.user.create({ data: parsed.data });
  return { error: null };
}
// The Client Component with the useActionState
'use client';

import { useActionState } from 'react';
import { createUser } from '@/app/actions';

function UserForm() {
  // The hook returns the state, the wrapped action, and the pending flag.
  const [state, formAction, pending] = useActionState(createUser, { error: null });

  return (
    <form action={formAction}>
      <input name="name" disabled={pending} />
      <input name="email" disabled={pending} />
      {state.error && <p role="alert">{state.error}</p>}
      <button type="submit" disabled={pending}>
        {pending ? 'Savingโ€ฆ' : 'Save'}
      </button>
    </form>
  );
}

The useActionState(createUser, { error: null }) is the mechanism that manages the state and the pending flag. The formAction is the wrapped action that the form’s action prop receives, and the action prop is the mechanism that works without the JavaScript . The state.error is the mechanism that displays the server error, and the pending is the mechanism that disables the inputs.

The two patterns are the different ends of the spectrum. The React Hook Form pattern is the client-heavy with the rich features, and the useActionState pattern is the server-heavy with the progressive enhancement. The Next.js documentation states the choice: the useActionState is the “recommended pattern for Server Actions”, and the React Hook Form is the choice “when you need client-side features like dynamic fields, complex validation, or the formState subscription” .


Complete Example Session

// ============================================
// PART 1: THE SERVER ACTION
// ============================================

'use server';

import { z } from 'zod';

const schema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

export async function createUser(formData: FormData) {
  const parsed = schema.safeParse({
    name: formData.get('name'),
    email: formData.get('email'),
  });

  if (!parsed.success) {
    return { error: 'Invalid input' };
  }

  await db.user.create({ data: parsed.data });
  return { error: null };
}

// ============================================
// PART 2: THE CLIENT FORM WITH THE REACT HOOK FORM
// ============================================

'use client';

import { useForm } from 'react-hook-form';
import { useTransition } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { createUser } from '@/app/actions';

const schema = z.object({
  name: z.string().min(2, 'At least 2 characters'),
  email: z.string().email('Invalid email'),
});

type FormValues = z.infer<typeof schema>;

function UserForm() {
  const [isPending, startTransition] = useTransition();
  const { register, handleSubmit, setError, formState: { errors } } = useForm<FormValues>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormValues) => {
    startTransition(async () => {
      const formData = new FormData();
      formData.append('name', data.name);
      formData.append('email', data.email);

      const result = await createUser(formData);

      if (result.error) {
        setError('root.server', { message: result.error });
      }
    });
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('name')} disabled={isPending} />
      {errors.name && <p>{errors.name.message}</p>}
      <input {...register('email')} disabled={isPending} />
      {errors.email && <p>{errors.email.message}</p>}
      {errors.root?.server && <p>{errors.root.server.message}</p>}
      <button disabled={isPending}>{isPending ? 'Savingโ€ฆ' : 'Save'}</button>
    </form>
  );
}

// ============================================
// PART 3: THE USE ACTION STATE PATTERN
// ============================================

'use client';

import { useActionState } from 'react';
import { createUser } from '@/app/actions';

function UserForm() {
  const [state, formAction, pending] = useActionState(createUser, { error: null });

  return (
    <form action={formAction}>
      <input name="name" disabled={pending} />
      <input name="email" disabled={pending} />
      {state.error && <p>{state.error}</p>}
      <button disabled={pending}>{pending ? 'Savingโ€ฆ' : 'Save'}</button>
    </form>
  );
}

// ============================================
// PART 4: THE TWO VALIDATIONS
// ============================================

// The client validation: the zodResolver(schema).
// The server validation: the schema.safeParse in the action.
// The two are the same shape.

// ============================================
// PART 5: THE PENDING STATE
// ============================================

// The useTransition's isPending.
// The useActionState's pending.

// ============================================
// PART 6: THE SERVER ERROR
// ============================================

// The setError("root.server", { message }).
// The state.error in the useActionState.

// ============================================
// PART 7: THE REVALIDATION
// ============================================

// The Server Action re-validates the input.
// The client validation is the convenience.

// ============================================
// PART 8: THE PROGRESSIVE ENHANCEMENT
// ============================================

// The React Hook Form requires the JavaScript.
// The useActionState works without the JavaScript.

// ============================================
// PART 9: THE CHOICE
// ============================================

// The React Hook Form: the dynamic fields, the watch, the formState.
// The useActionState: the simple form, the progressive enhancement.

// ============================================
// PART 10: THE CHECKLIST
// ============================================

// [ ] The Server Action has the 'use server' directive.
// [ ] The Server Action re-validates the input.
// [ ] The client validation is the zodResolver.
// [ ] The useTransition tracks the Server Action.
// [ ] The setError maps the server error.
// [ ] The useActionState is the progressive-enhancement alternative.

The ten parts covered the Server Action, the client form with the React Hook Form, the useActionState pattern, the two validations, the pending state, the server error, the revalidation, the progressive enhancement, the choice, and the checklist. The through-line is that the Server Action is the security boundary, and the React Hook Form is the client convenience.


Quick Reference

The two patterns

AspectReact Hook FormuseActionState
The validationThe client resolver + the serverThe server only
The pendingThe useTransition‘s isPendingThe hook’s pending
The errorThe setErrorThe state.error
The progressive enhancementNoYes
The client featuresThe full setThe limited

The Server Action

AspectDetail
The directiveThe 'use server'
The inputThe FormData
The validationThe schema.safeParse
The returnThe { error }
The revalidationThe required

The pending state

MechanismSource
The useTransitionThe React Hook Form pattern
The useActionStateThe progressive-enhancement pattern
The formState.isSubmittingThe React Hook Form’s own

The error display

SourcePath
The client validationThe errors.field
The server validationThe errors.root.server
The useActionStateThe state.error

Best Practices

โœ… Do This:

// Re-validate on the server
const parsed = schema.safeParse(data);                             // โœ…

// Use the useTransition for the pending state
startTransition(async () => { await createUser(formData); });      // โœ…

// Map the server error to the form
setError('root.server', { message: result.error });                // โœ…

// Use the useActionState for the progressive enhancement
const [state, formAction, pending] = useActionState(createUser, {}); // โœ…

โŒ Don’t Do This:

// Trust the client validation alone
await db.user.create({ data }); // the bypass risk                 // โŒ

// Call the Server Action without the transition
await createUser(formData); // the pending state is not tracked    // โŒ

// Assume the React Hook Form works without the JavaScript
// The progressive enhancement requires the useActionState.        // โŒ

Common Pitfalls

PitfallWhy It HappensFix
The Server Action is not revalidatingThe client validation is trustedAdd the schema.safeParse
The pending state is not shownThe useTransition is missingUse the useTransition
The server error is not displayedThe setError is missingMap the root.server
The form does not work without the JSThe React Hook Form is usedUse the useActionState

Real-World Examples

1. The Server Action

'use server'; export async function createUser(formData: FormData) {}

2. The server validation

const parsed = schema.safeParse({ name: formData.get('name') });

3. The client resolver

useForm({ resolver: zodResolver(schema) })

4. The useTransition

const [isPending, startTransition] = useTransition();

5. The Server Action call

startTransition(async () => { await createUser(formData); })

6. The server error

setError('root.server', { message: result.error })

7. The useActionState

const [state, formAction, pending] = useActionState(createUser, {});

8. The form action

<form action={formAction}>...</form>

9. The progressive enhancement

// The useActionState works without the JavaScript.

10. The revalidation

// The Server Action re-validates the input.

Visual

The Two Patterns

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE TWO PATTERNS                                            โ”‚
โ”‚                                                              โ”‚
โ”‚  React Hook Form              useActionState                 โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”‚
โ”‚  โ”‚ The client resolver โ”‚     โ”‚ The server action   โ”‚        โ”‚
โ”‚  โ”‚ The useTransition   โ”‚     โ”‚ The hook's pending  โ”‚        โ”‚
โ”‚  โ”‚ The setError        โ”‚     โ”‚ The state.error     โ”‚        โ”‚
โ”‚  โ”‚ The client features โ”‚     โ”‚ The progressive     โ”‚        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Two Validations

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE TWO VALIDATIONS                                         โ”‚
โ”‚                                                              โ”‚
โ”‚  The client                                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The zodResolver     โ”‚  The UX feedback                   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The Server Action   โ”‚  The security boundary             โ”‚
โ”‚  โ”‚ The schema.safeParseโ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The database        โ”‚  The trusted data                  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Pending State

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE PENDING STATE                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  The React Hook Form:                                        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ useTransition       โ”‚  The isPending                     โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The disabled state  โ”‚  The UI feedback                   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  The useActionState:                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The hook's pending  โ”‚  The automatic pending flag        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
The Server ActionThe 'use server' directive
The revalidationThe required on the server
The React Hook Form patternThe client resolver + the useTransition
The useActionState patternThe server-only + the progressive enhancement
The pendingThe isPending or the pending
The server errorThe setError('root.server') or the state.error

Key takeaways:

  • The React Hook Form and the Server Actions are the two separate layers. The useForm validates on the client, and the Server Action validates on the server. The two validations are the defense in depth, and the server validation is the security boundary .
  • The Server Action re-validates the input. The client validation is the UX convenience, and the bypass is the reason the server validation is the requirement. The schema.safeParse is the mechanism that keeps the invalid data out of the database .
  • The useTransition is the pending-state mechanism for the Server Action. The isPending is the boolean that the UI reads, and the startTransition is the wrapper that tracks the action’s promise .
  • The setError maps the server error to the form. The errors.root.server is the path that the server error uses, and the root error is the reason the general failure is visible .
  • The useActionState is the progressive-enhancement alternative. The hook manages the state and the pending flag, and the form works without the JavaScript. The choice between the two patterns is the client features against the progressive enhancement .

Remember: The React Hook Form and the Server Actions are the two layers. The client resolver is the UX, and the server validation is the security. The useTransition tracks the pending state, and the setError maps the server error. The useActionState is the progressive-enhancement alternative. The two validations are the defense in depth, and the depth is the reason the Server Action does not trust the client.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!