Toolkit
Beta

Custom Hooks

Easy state management and API integration of waitlist features in your frontend.

useWaitlist()

We create a single hook to implement waitlist functionality on the client. This allows us to have multiple entry points for the waitlist without duplicating logic.

waitlist_hook.ts
import { useMutation } from '@tanstack/react-query';
import { useSearchParams } from 'next/navigation';
import React, { useRef, useState } from 'react';
import { z } from 'zod';
 
const isValidEmail = (email: string) => z.string().email().safeParse(email).success;
 
export function useWaitlist() {
  const searchParams = useSearchParams();
 
  const [email, setEmail] = useState('');
  const [errorMessage, setErrorMessage] = useState<string | undefined>();
  const emailInputRef = useRef<HTMLInputElement>(null);
 
  const {
    data: signUpInfo,
    error,
    isPending,
    isSuccess,
    mutate,
  } = useMutation({
    mutationFn: async (request: WaitlistSignup) => {
      const response = await fetch('/api/waitlist', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(request),
      });
      const result = await response.json();
      if (!response.ok) {
        throw new Error(result.error ?? 'Something went wrong.');
      }
      return result as PublicWaitlistEntry;
    },
  });
 
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setErrorMessage(undefined);
 
    if (!isValidEmail(email)) {
      setErrorMessage('Please enter a valid email address.');
      emailInputRef.current?.focus();
      return;
    }
 
    const referredByCode = searchParams.get(REFERRAL_CODE_QUERY_KEY) ?? undefined;
 
    mutate({ email, referredByCode });
  };
 
  const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setEmail(e.target.value);
    setErrorMessage(undefined);
  };
 
  return {
    email,
    emailInputRef,
    isSubmitting: isPending,
    isDone: isSuccess,
    error: error?.message ?? errorMessage,
    signUpInfo,
    handleSubmit,
    handleEmailChange,
  };
}

If you prefer to avoid additional abstraction, you can move this logic to the waitlist component.

PropertyUsage
emailBind to input value to control the email field.
emailInputRefAttach to input element for focus management.
isSubmittingShow loading state during form submission.
isDoneDisplay success message or redirect after signup.
errorShow validation or server errors to user.
signUpInfoAccess referral code and URL after successful signup.
handleSubmitBind to form onSubmit to process registration.
handleEmailChangeBind to input onChange to update email value.

Key Design Decisions

  1. Maintains form state internally to reduce boilerplate in consuming components.
  2. Automatically handles referral codes from URL search parameters.
  3. Provides input ref for programmatic focus management, i.e., after validation error.
  4. Separates validation errors from server errors for better UX.
  5. Manages loading, success, and error states automatically.
  6. Clears error messages on input change for better user feedback.
  7. Returns minimal interface with typed handlers and state values.

On this page