45 minlesson

Phase 2: Package Input Form & Validation

Phase 2: Package Input Form & Validation

Overview

Build a sophisticated, user-friendly form for collecting package and shipping information. You'll use React 19's latest features including Server Actions and the useFormStatus hook, implement validation using the Chain of Responsibility pattern, and create reusable custom hooks.

Learning Objectives

By the end of this phase, you will:

  • Build multi-step forms with React 19 Server Actions
  • Implement Chain of Responsibility pattern for validation
  • Create custom hooks for complex form state management
  • Handle form errors and provide real-time user feedback
  • Build accessible form components with proper ARIA attributes

Requirements

1. Form Structure & Components

Multi-Step Form Flow:

  1. Step 1: Package Details

    • Package type selection (envelope, box, tube, custom)
    • Dimensions input (length, width, height)
    • Unit selector (inches/cm)
    • Weight input with unit selector (lbs/kg)
    • Declared value (optional)
  2. Step 2: Origin & Destination

    • Origin address form
    • Destination address form
    • Address validation with real-time feedback
    • Saved addresses quick-select (optional enhancement)
  3. Step 3: Shipping Options

    • Service speed selection (overnight, 2-day, standard, economy)
    • Additional services checkboxes:
      • Signature required
      • Insurance (shows value input when checked)
      • Fragile handling
      • Saturday delivery
    • Carrier filter (optional: select specific carriers)
  4. Step 4: Review & Submit

    • Summary of all inputs
    • Edit buttons for each step
    • Calculate rates button

Required React Components:

Create the following components in src/components/forms/:

Main Form Container:

  • RateCalculatorForm - Orchestrates the multi-step form, manages overall state

Step Components:

  • PackageDetailsStep - Handles package dimensions, weight, and type input
  • AddressStep - Manages origin and destination address forms
  • ShippingOptionsStep - Collects service speed and additional options
  • ReviewStep - Displays summary of all inputs with edit capability

Reusable Form Field Components:

  • DimensionsInput - Input group for length, width, height with unit selector
  • WeightInput - Weight input with unit selector (lbs/kg)
  • AddressForm - Complete address input form with validation error display
  • ServiceSpeedSelector - Radio group or select for service speed options

Form Controls:

  • FormNavigation - Previous/Next buttons with step progress indicator
  • SubmitButton - Uses React 19's useFormStatus hook for pending state

2. Chain of Responsibility Pattern - Validation

Pattern Implementation Goal: Apply the Chain of Responsibility pattern (from TypeScript Design Patterns course, Topic 16) to create a flexible validation system where each validator can pass data to the next validator in the chain.

File to Create: src/services/validators/validation-chain.ts

Core Interfaces and Types:

Define the following interfaces:

  • Validator<T> interface with:
    • setNext() method to chain validators
    • validate() method that returns ValidationResult
  • ValidationResult interface with isValid boolean and errors array
  • ValidationError interface with field name, message, and error code

Abstract Base Class:

Create an abstract BaseValidator<T> class that:

  • Implements the Validator<T> interface
  • Maintains a reference to the next validator in the chain
  • Implements setNext() to chain validators (returns the validator for fluent API)
  • Implements validate() to:
    • Call the abstract doValidation() method
    • If validation fails, return the result immediately
    • If validation passes and there's a next validator, call it
    • If validation passes and no next validator, return success
  • Declares abstract doValidation() method for subclasses to implement

Required Validator Implementations:

Create the following concrete validator classes:

For Address Validation:

  • RequiredFieldsValidator - Ensures street1, city, state, postalCode, country are not empty
  • PostalCodeFormatValidator - Validates postal code format based on country (US: 5 or 9 digits)
  • StateCodeValidator - Validates state code is valid for the given country

For Package Validation:

  • DimensionsValidator - Ensures dimensions are positive numbers and within carrier limits (e.g., length + 2*(width + height) < 165 inches for USPS)
  • WeightValidator - Ensures weight is positive and under common carrier limit (150 lbs)

Factory Functions:

Create src/services/validators/index.ts with factory functions:

  • createAddressValidationChain() - Chains address validators in logical order
  • createPackageValidationChain() - Chains package validators in logical order

Each factory should instantiate validators and chain them using setNext(), returning the first validator in the chain.

3. Custom Hooks

Hook Development Goal: Create reusable React hooks to encapsulate complex form logic and calculations.

Hook 1: usePackageForm

Create src/hooks/usePackageForm.ts that manages the entire multi-step form state.

State Management:

  • Track current step number
  • Maintain partial state for package, origin address, destination address, and shipping options
  • Store validation errors by field name

Actions to Implement:

  • Update functions for each section (package, origin, destination, options)
  • nextStep() - Validates current step before advancing, returns boolean success
  • previousStep() - Navigates to previous step
  • goToStep() - Jumps to specific step (for edit functionality)
  • submitForm() - Async function to submit complete form
  • reset() - Clears all form data

Use useState for state management and integrate validation chains before allowing step transitions.

Hook 2: useAddressValidation

Create src/hooks/useAddressValidation.ts for address validation with real-time feedback.

Features to Implement:

  • Track validation errors array
  • Track isValidating loading state
  • validate() - Async function to validate complete address using validation chain
  • validateField() - Synchronous field-level validation for immediate feedback
  • Debounce validation calls (300-500ms) to avoid excessive validations while user types

Hook 3: useDimensionalWeight

Create src/hooks/useDimensionalWeight.ts to calculate billable weight.

Calculation Logic:

  • Calculate dimensional weight using formula: (L × W × H) / 139 for inches, or / 5000 for cm
  • Convert weight units if necessary
  • Determine billable weight (greater of actual or dimensional)
  • Return whether dimensional weight was applied
  • Use useMemo to memoize expensive calculations and prevent recalculation on every render

4. React 19 Server Actions Integration

Goal: Implement server-side validation using React 19's Server Actions feature (covered in React 19 Mastery course, Topics 5-6).

Server Action to Create: src/app/api/validate-address/route.ts

Mark the file with 'use server' directive to indicate this is a Server Action.

Implementation Requirements:

  1. Schema Definition:

    • Use Zod to create an AddressSchema that validates:
      • street1: minimum 1 character
      • city: minimum 1 character
      • state: exactly 2 characters
      • postalCode: minimum 5 characters
      • country: exactly 2 characters
  2. Server Action Function:

    • Create an async validateAddress() function that accepts FormData
    • Extract data from FormData using Object.fromEntries()
    • Use Zod's safeParse() for initial validation
    • If Zod validation fails, return error response with flattened field errors
    • If Zod validation passes, apply your validation chain for business logic validation
    • Return success/failure response with appropriate error messages

Client Component Integration:

In your address form component (AddressForm):

  • Mark as client component with 'use client'
  • Import React 19's useFormStatus hook
  • Import your server action
  • Use the pending state from useFormStatus to show loading state
  • Create form with action prop pointing to your validation handler
  • Disable submit button while pending is true
  • Show appropriate loading text ("Validating..." vs "Continue")

5. Accessibility Requirements

All form components must follow WCAG 2.1 Level AA guidelines. Implement the following for each form field:

Required ARIA Attributes:

  • aria-label or associated <label> element for all inputs
  • aria-required="true" for required fields
  • aria-invalid set to true/false based on validation state
  • aria-describedby linking input to error message when errors exist
  • role="alert" on error message containers for screen reader announcements

Keyboard Navigation:

  • Tab key navigates through all form fields in logical order
  • Enter key submits forms or advances steps
  • Escape key cancels operations where appropriate
  • Focus is moved to first invalid field on validation failure

Visual Indicators:

  • Required fields marked with asterisk (*) and text label
  • Error messages displayed clearly near the relevant field
  • Focus indicators visible for keyboard navigation
  • Error states visually distinct (color + icon, not color alone)

Pattern to Follow: Each input should have a unique ID, proper labeling, error messaging connected via aria-describedby, and error paragraphs with role="alert" for dynamic announcements.

6. Form Persistence

Goal: Prevent data loss by persisting form state to browser localStorage.

File to Create: src/lib/form-storage.ts

Implementation Requirements:

Define a storage key constant (e.g., 'rate-calculator-form-state') to use consistently.

Functions to Implement:

  1. saveFormState(state: PackageFormState): void

    • Serialize state to JSON
    • Save to localStorage using the storage key
    • Call this function on every form state change (debounce if performance is an issue)
  2. loadFormState(): PackageFormState | null

    • Retrieve data from localStorage
    • Parse JSON and return typed state object
    • Return null if no saved state exists
    • Handle JSON parse errors gracefully
  3. clearFormState(): void

    • Remove saved state from localStorage
    • Call after successful form submission

UX Enhancement:

  • On application load, check if saved state exists
  • If found, show a "Resume previous session?" prompt to the user
  • Allow users to choose between resuming or starting fresh

Deliverables

By the end of Phase 2, you must have:

  • Multi-step form with all 4 steps implemented
  • All required React components created and functional
  • Chain of Responsibility pattern for validation (minimum 5 validators)
  • Three custom hooks: usePackageForm, useAddressValidation, useDimensionalWeight
  • Server Action for address validation using React 19 features
  • Form persistence with localStorage
  • Full accessibility compliance (WCAG 2.1 Level AA)
  • Real-time validation feedback on all fields
  • Responsive design working on mobile and desktop
  • Unit tests for validation chains (minimum 70% coverage)

Validation Checklist

Before moving to Phase 3:

  • All form steps render without errors
  • Validation chain prevents invalid data from progressing
  • Custom hooks manage state correctly
  • Form persists and restores from localStorage
  • Server Action validates addresses correctly
  • All accessibility requirements met (test with screen reader)
  • Form works on mobile viewport (320px width minimum)
  • No TypeScript errors or warnings
  • Validation tests pass: npm test

Testing Requirements

Unit Tests for Validators:

Create test file: src/services/validators/__tests__/validation-chain.test.ts

Write tests to verify:

  • Validator chain rejects addresses with empty required fields
  • Invalid postal code formats are caught
  • Valid addresses pass all validators in the chain
  • Each individual validator works correctly in isolation
  • Chain stops at first validation failure

Integration Tests for Hooks:

Create test file: src/hooks/__tests__/usePackageForm.test.ts

Write tests to verify:

  • Form does not advance to next step when current step has validation errors
  • Form state is correctly persisted to localStorage on changes
  • Form state is restored from localStorage on component mount
  • Step navigation works correctly (forward, backward, jump to step)
  • Form submission triggers validation of all steps

Resources

  • Course: React 19 Mastery (Topics 3, 5, 6, 7)
  • Course: TypeScript Design Patterns (Topic 16: Chain of Responsibility)
  • Course: JavaScript ES6+ Foundations (Topic 2: Functions, Topic 7: DOM)
  • React 19 useFormStatus
  • WCAG 2.1 Form Guidelines

Common Pitfalls to Avoid

  1. Not validating on step transition: Validate before allowing next step
  2. Poor error UX: Show errors clearly, don't block users from reviewing other steps
  3. Ignoring accessibility: Screen readers and keyboard-only users must be able to use the form
  4. Not handling async validation: Use loading states for server-side validation
  5. Losing form state: Always persist to localStorage to prevent data loss

Estimated Time

  • Form UI components: 4 hours
  • Validation chain implementation: 3 hours
  • Custom hooks: 2 hours
  • Server Actions integration: 1 hour
  • Testing: 2 hours
  • Total: 12 hours

Next Steps

Once you've completed all deliverables and passed the validation checklist, proceed to Phase 3: Rate Calculation Engine with Design Patterns where you'll implement the core business logic using Strategy, Factory, Decorator, and Adapter patterns.