Registration form example

This is a simple login form:

  • If you enter an email address that doesn't end with @functn.com you will see an error message from the server.

Create an account

Please enter your full name without any middle names
Please enter a valid email address. Example: test@test.com
Please enter your phone number with the country code. Example: +43
Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, and one number.
Favorite Animal (Required)
Country (Required)
Preferred Contact Method (Required)
Read our full Terms and Conditions

registration-form.tsx

'use client';
 
import Link from 'next/link';
import { Button } from '@/ui/components/button/button';
import { Checkbox } from '@/ui/components/forms/checkbox';
import { Form } from '@/ui/components/forms/form';
import { useFormAction } from '@/ui/components/forms/hooks/use-form-action';
import { Radio, RadioGroup } from '@/ui/components/forms/radio';
import { Select, SelectItem } from '@/ui/components/forms/select';
import { TextField } from '@/ui/components/forms/text-field';
import {
  FieldErrorSummary,
  FormErrors,
  ServerError,
  validators,
} from '@/ui/components/forms/validation';
import { registerAction } from './action';
 
export const RegistrationForm = () => {
  const form = useFormAction<{
    name: string;
    email: string;
    phone: string;
    password: string;
    favoriteAnimal: string;
    contactMethod: string;
    'terms-and-conditions': string;
  }>(registerAction);
 
  return (
    <Form
      action={form.action}
      className="flex max-w-lg flex-col gap-4 rounded-lg border-2 border-gray-200 p-4 shadow-xl"
    >
      <h2 className="text-3xl font-bold">Create an account</h2>
 
      <FormErrors>
        <ServerError
          errorMessage={form.serverError}
          title="Could not register"
        />
        <FieldErrorSummary
          title="There are errors in your submission"
          subtitle="Please correct your input in these fields:"
          fieldLabels={{
            name: 'Name',
            email: 'Email Address',
            phone: 'Phone Number',
            password: 'Password',
            favoriteAnimal: 'Favorite Animal',
            contactMethod: 'Preferred Contact Method',
            'terms-and-conditions': 'Terms and Conditions',
          }}
        />
      </FormErrors>
 
      <TextField
        id="name"
        name="name"
        label="Name"
        type="text"
        description="Please enter your full name without any middle names"
        defaultValue={form.defaultValues.name}
        autoComplete="name"
        isRequired
        validate={(value) => {
          if (!value) {
            return 'Please enter your name';
          }
        }}
      />
 
      <TextField
        id="email"
        name="email"
        label="Email Address"
        type="email"
        description="Please enter a valid email address. Example: test@test.com"
        defaultValue={form.defaultValues.email}
        autoComplete="email"
        validate={(value) => {
          if (!validators.email(value)) {
            return 'Invalid email address';
          }
        }}
        isRequired
      />
 
      <TextField
        id="phone"
        name="phone"
        label="Phone Number"
        type="tel"
        description="Please enter your phone number with the country code. Example: +43"
        defaultValue={form.defaultValues.phone}
        autoComplete="tel"
      />
 
      <TextField
        id="password"
        name="password"
        label="Password"
        type="password"
        description="Password must be at least 8 characters long and contain at least one
          uppercase letter, one lowercase letter, and one number."
        defaultValue={form.defaultValues.password}
        autoComplete="new-password"
        isRequired
        validate={(value) => {
          if (value.length < 8) {
            return 'Password must be at least 8 characters long';
          }
          if (!/[A-Z]/.test(value)) {
            return 'Password must contain at least one uppercase letter';
          }
          if (!/[a-z]/.test(value)) {
            return 'Password must contain at least one lowercase letter';
          }
          if (!/[0-9]/.test(value)) {
            return 'Password must contain at least one number';
          }
        }}
      />
 
      <Select
        id="favoriteAnimal"
        name="favoriteAnimal"
        label="Favorite Animal"
        isRequired
        defaultValue={form.defaultValues.favoriteAnimal}
        validate={(value) => {
          if (!value) {
            return 'Please select an animal';
          }
          if (value !== 'dog') {
            return 'Please select dogs as your favorite animal';
          }
        }}
      >
        <SelectItem id="cat">Cat</SelectItem>
        <SelectItem id="dog">Dog</SelectItem>
        <SelectItem id="kangaroo">Kangaroo</SelectItem>
        <SelectItem id="panda">Panda</SelectItem>
        <SelectItem id="snake">Snake</SelectItem>
        <SelectItem id="lion">Lion</SelectItem>
        <SelectItem id="tiger">Tiger</SelectItem>
        <SelectItem id="elephant">Elephant</SelectItem>
        <SelectItem id="giraffe">Giraffe</SelectItem>
        <SelectItem id="zebra">Zebra</SelectItem>
        <SelectItem id="hippo">Hippo</SelectItem>
        <SelectItem id="rhino">Rhino</SelectItem>
        <SelectItem id="leopard">Leopard</SelectItem>
        <SelectItem id="cheetah">Cheetah</SelectItem>
        <SelectItem id="monkey">Monkey</SelectItem>
        <SelectItem id="fox">Fox</SelectItem>
        <SelectItem id="wolf">Wolf</SelectItem>
      </Select>
 
      <RadioGroup
        id="contactMethod"
        name="contactMethod"
        label="Preferred Contact Method"
        isRequired
        defaultValue={form.defaultValues.contactMethod}
        validate={(value) => {
          console.log('RADIO VALIDATION', value);
          if (!value) {
            return 'Please select a contact method';
          }
          if (value === 'pigeon') {
            return 'Please select a modern contact method';
          }
        }}
        orientation="vertical"
      >
        <Radio value="email">Email</Radio>
        <Radio value="phone">Phone</Radio>
        <Radio value="pigeon">Carrier Pigeon</Radio>
      </RadioGroup>
 
      <Checkbox
        id="terms-and-conditions"
        name="terms-and-conditions"
        value="terms-and-conditions"
        label="I agree to the terms and conditions"
        defaultSelected={!!form.defaultValues['terms-and-conditions']}
        description={
          <>
            Read our full{' '}
            <Link
              href="/terms-and-conditions"
              className="text-primary-500 hover:underline"
            >
              Terms and Conditions
            </Link>
          </>
        }
        isRequired
        validate={(value) => {
          if (!value) {
            return 'Please agree to the terms and conditions';
          }
        }}
        className="pt-2"
      />
 
      <div>
        <Button type="submit">Submit</Button>
      </div>
    </Form>
  );
};

action.ts

import { createUser } from '@/api/auth';
import { FormAction } from '@/ui/components/forms/types';
import { getFormValues } from '@/ui/components/forms/util';
 
export const registerAction: FormAction = async (currentState, formData) => {
  const values = getFormValues<{
    name: string;
    email: string;
    phone: string;
    password: string;
    favoriteAnimal: string;
    contactMethod: string;
    'terms-and-conditions': string;
  }>(formData);
 
  // We can do some basic input validation, this should have all been caught
  // by the client-side validation so in the case this fails we either have
  // a bug with the client code or someone is calling this API maliciously.
  // Either way, just return a generic error message to the user and bail out.
  if (!values.email || !values.password) {
    return {
      status: 'error',
      formData,
      serverError: 'Something went wrong',
    };
  }
 
  try {
    await createUser(values.email, values.password);
  } catch (error) {
    return {
      status: 'error',
      formData,
      serverError: 'Account must be created with an @functn.com email address',
    };
  }
 
  return {
    status: 'success',
    formData,
  };
};