typescript 113 lines · 1 tab

React Hook Form with async validation

Maya Patel Jan 2026
1 tab
import { useForm } from 'react-hook-form'
import { useState } from 'react'
import api from '@/services/api'

interface SignupFormData {
  email: string
  username: string
  password: string
}

export function SignupForm() {
  const [validatingUsername, setValidatingUsername] = useState(false)

  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<SignupFormData>()

  const checkUsernameAvailability = async (username: string) => {
    if (username.length < 3) {
      return 'Username must be at least 3 characters'
    }

    setValidatingUsername(true)

    try {
      const { data } = await api.get(`/auth/check-username?username=${username}`)

      if (!data.available) {
        return 'Username is already taken'
      }

      return true
    } catch (error) {
      return 'Unable to verify username'
    } finally {
      setValidatingUsername(false)
    }
  }

  const onSubmit = async (data: SignupFormData) => {
    try {
      await api.post('/auth/signup', data)
      window.location.href = '/dashboard'
    } catch (error: any) {
      console.error('Signup failed:', error)
    }
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      <div>
        <label htmlFor="email">Email</label>
        <input
          {...register('email', {
            required: 'Email is required',
            pattern: {
              value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
              message: 'Invalid email address',
            },
          })}
          type="email"
          id="email"
          className="w-full px-3 py-2 border rounded"
        />
        {errors.email && <p className="text-red-600 text-sm mt-1">{errors.email.message}</p>}
      </div>

      <div>
        <label htmlFor="username">Username</label>
        <div className="relative">
          <input
            {...register('username', {
              required: 'Username is required',
              validate: checkUsernameAvailability,
            })}
            id="username"
            className="w-full px-3 py-2 border rounded"
          />
          {validatingUsername && (
            <div className="absolute right-3 top-1/2 transform -translate-y-1/2">
              <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600" />
            </div>
          )}
        </div>
        {errors.username && <p className="text-red-600 text-sm mt-1">{errors.username.message}</p>}
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input
          {...register('password', {
            required: 'Password is required',
            minLength: { value: 8, message: 'Password must be at least 8 characters' },
          })}
          type="password"
          id="password"
          className="w-full px-3 py-2 border rounded"
        />
        {errors.password && <p className="text-red-600 text-sm mt-1">{errors.password.message}</p>}
      </div>

      <button
        type="submit"
        disabled={isSubmitting || validatingUsername}
        className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
      >
        {isSubmitting ? 'Creating account...' : 'Sign Up'}
      </button>
    </form>
  )
}
1 file · typescript Explain with highlit

Async validation checks constraints that require server communication, like username availability or email uniqueness. React Hook Form's validate option accepts async functions that return error messages or true. I debounce async validators to avoid excessive requests and show loading states during validation. The form submission waits for all async validations to complete. For better UX, I show validation results inline as users type, with clear messaging about what's being checked. Async validation combines with Zod schemas using custom refinements. The key is balancing responsiveness (early feedback) with efficiency (not spamming the server). This pattern provides real-time validation without sacrificing performance.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 2 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
typescript
export type Settled<R> =
  | { status: 'fulfilled'; value: R }
  | { status: 'rejected'; reason: unknown };

export interface ConcurrencyOptions {
  limit: number;

Simple concurrency limiter for batch operations

node concurrency async
by codesnips 2 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
  timeout: 15000,

Axios API client with interceptors

react axios api
by Maya Patel 1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs

Share this code

Here's the card — post it anywhere.

React Hook Form with async validation — share card
Link copied