Login Form Example

A simple login form using useActionForm with automatic Zod error mapping from the server action.

Try taken@example.com to see server-side error mapping.

Source Code

Server Action

'use server'
import { z } from 'zod'

const loginSchema = z.object({
  email: z.string().email('Please enter a valid email'),
  password: z.string().min(8, 'Min 8 characters'),
})

export async function loginAction(data: z.infer<typeof loginSchema>) {
  await new Promise(r => setTimeout(r, 1000)) // simulate delay

  const parsed = loginSchema.safeParse(data)
  if (!parsed.success) {
    return { errors: parsed.error.flatten().fieldErrors }
  }

  if (parsed.data.email === 'taken@example.com') {
    return { errors: { email: ['Already registered'] } }
  }

  return { success: true }
}

Client Component

'use client'
import { useActionForm } from 'hookform-action'
import { loginAction } from './actions'

export function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isPending, isSubmitSuccessful },
  } = useActionForm(loginAction, {
    defaultValues: { email: '', password: '' },
  })

  if (isSubmitSuccessful) return <p>✓ Logged in!</p>

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

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

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