Skip to content

Date of Birth Input Component

Overview

The DOBFieldInput component provides a user-friendly date of birth input using three separate text fields (day, month, year) instead of the native HTML5 date picker. This approach offers:

  • Consistent behavior across browsers and devices
  • Better mobile usability (no awkward date picker wheels)
  • Locale-aware field ordering (MDY vs DMY)
  • Clear validation feedback

Architecture

Files

File Purpose
components/ui/DOBFieldInput.tsx Main reusable component
lib/hooks/useDateFormat.ts Domain-based format detection
lib/utils/dateValidation.ts Validation and formatting utilities

Component Props

interface DOBFieldInputProps {
  value: string;              // YYYY-MM-DD format (backend compatible)
  onChange: (value: string) => void;
  disabled?: boolean;
  error?: string;             // External error message to display
  showHint?: boolean;         // Show format hint (e.g., "MM/DD/YYYY")
}

Date Format Detection

The useDateFormat hook determines field order based on the current domain:

Domain Format Field Order
localhost MDY Month → Day → Year
oterahealth.* MDY Month → Day → Year
oktahealth.* DMY Day → Month → Year
Other domains DMY Day → Month → Year
import { useDateFormat } from "@/lib/hooks/useDateFormat";

const { format, order, placeholder } = useDateFormat();
// format: "MDY" or "DMY"
// order: ["month", "day", "year"] or ["day", "month", "year"]
// placeholder: "MM/DD/YYYY" or "DD/MM/YYYY"

Features

Auto-Advance

When a field reaches its maximum length (2 digits for day/month, 4 for year), focus automatically advances to the next field.

Backspace Navigation

Pressing backspace on an empty field moves focus to the previous field.

Validation on Blur

When leaving a field, the component: 1. Auto-corrects invalid values (e.g., month "15" → "12") 2. Pads single digits with leading zeros (e.g., "5" → "05") 3. Validates the complete date if all fields are filled 4. Shows appropriate error messages

Date Preview

When all three fields contain valid values, a human-readable date string (e.g., "March 5, 1990") appears below the input fields. This lets users quickly confirm the date they entered is correct, which is especially helpful when the field order differs between locales. The preview updates live as fields change and disappears if any field is empty or invalid.

Validation Rules

  • Day: 1-31 (refined by month and year)
  • Month: 1-12
  • Year: 1900 to current year
  • Future dates: Not allowed
  • Leap years: Feb 29 only valid in leap years
  • Invalid dates: Feb 30, Apr 31, etc. are rejected

Usage Examples

Basic Usage

import DOBFieldInput from "@/components/ui/DOBFieldInput";

const [dateOfBirth, setDateOfBirth] = useState("");

<DOBFieldInput
  value={dateOfBirth}
  onChange={setDateOfBirth}
/>

With External Error

<DOBFieldInput
  value={dateOfBirth}
  onChange={setDateOfBirth}
  error={formErrors.dateOfBirth}
/>

Disabled State

<DOBFieldInput
  value={dateOfBirth}
  onChange={setDateOfBirth}
  disabled={isSubmitting}
/>

Without Format Hint

<DOBFieldInput
  value={dateOfBirth}
  onChange={setDateOfBirth}
  showHint={false}
/>

Data Flow

User Input → Local State (refs + state) → Validation → Parent (YYYY-MM-DD)
                                                         Backend API
  1. User types in individual fields
  2. Values are tracked in refs (synchronous) and state (for display)
  3. On blur, values are validated and formatted
  4. Parent receives YYYY-MM-DD format string via onChange
  5. Backend APIs expect and return YYYY-MM-DD format

Validation Utilities

validateDateOfBirth(day, month, year)

Returns validation result with error details:

interface DateValidationResult {
  isValid: boolean;
  error?: string;        // Translation key
  errorField?: "day" | "month" | "year";
}

formatDateForBackend(day, month, year)

Converts individual fields to YYYY-MM-DD format:

formatDateForBackend("5", "3", "1990") // → "1990-03-05"

parseDateFromBackend(dateString)

Converts YYYY-MM-DD to individual fields:

parseDateFromBackend("1990-03-05") // → { day: "05", month: "03", year: "1990" }

Translations

The component uses the following translation keys:

{
  "dob": {
    "day": "DD",
    "month": "MM",
    "year": "YYYY",
    "dayLabel": "Day",
    "monthLabel": "Month",
    "yearLabel": "Year",
    "errors": {
      "invalidDay": "Invalid day",
      "invalidMonth": "Invalid month",
      "invalidYear": "Invalid year",
      "futureDate": "Date cannot be in the future"
    }
  }
}

Where It's Used

Component Context
DOBInput.tsx Patient onboarding - DOB verification step
InviteNewPatientModal.tsx Therapist inviting new patient
ResendInviteModal.tsx Collecting DOB when resending invite
PatientManagementModal.tsx Editing patient details

Technical Notes

Race Condition Handling

The component uses refs to track the latest typed values synchronously, avoiding issues with React's batched state updates when auto-advance triggers blur events.

SSR Compatibility

The useDateFormat hook defaults to DMY format during server-side rendering and updates to the correct format on client mount.

Controlled Input

The component is fully controlled - the parent's value prop is the source of truth, and all changes are communicated via onChange.