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:
-
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)
-
Step 2: Origin & Destination
- Origin address form
- Destination address form
- Address validation with real-time feedback
- Saved addresses quick-select (optional enhancement)
-
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)
-
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 inputAddressStep- Manages origin and destination address formsShippingOptionsStep- Collects service speed and additional optionsReviewStep- Displays summary of all inputs with edit capability
Reusable Form Field Components:
DimensionsInput- Input group for length, width, height with unit selectorWeightInput- Weight input with unit selector (lbs/kg)AddressForm- Complete address input form with validation error displayServiceSpeedSelector- Radio group or select for service speed options
Form Controls:
FormNavigation- Previous/Next buttons with step progress indicatorSubmitButton- Uses React 19'suseFormStatushook 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 validatorsvalidate()method that returns ValidationResult
ValidationResultinterface with isValid boolean and errors arrayValidationErrorinterface 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
- Call the abstract
- 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 emptyPostalCodeFormatValidator- 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 ordercreatePackageValidationChain()- 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 successpreviousStep()- Navigates to previous stepgoToStep()- Jumps to specific step (for edit functionality)submitForm()- Async function to submit complete formreset()- 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
isValidatingloading state validate()- Async function to validate complete address using validation chainvalidateField()- 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
useMemoto 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:
-
Schema Definition:
- Use Zod to create an
AddressSchemathat validates:- street1: minimum 1 character
- city: minimum 1 character
- state: exactly 2 characters
- postalCode: minimum 5 characters
- country: exactly 2 characters
- Use Zod to create an
-
Server Action Function:
- Create an async
validateAddress()function that acceptsFormData - 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
- Create an async
Client Component Integration:
In your address form component (AddressForm):
- Mark as client component with
'use client' - Import React 19's
useFormStatushook - Import your server action
- Use the
pendingstate fromuseFormStatusto show loading state - Create form with
actionprop pointing to your validation handler - Disable submit button while
pendingis 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-labelor associated<label>element for all inputs -
aria-required="true"for required fields -
aria-invalidset to true/false based on validation state -
aria-describedbylinking 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:
-
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)
-
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
-
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
- Not validating on step transition: Validate before allowing next step
- Poor error UX: Show errors clearly, don't block users from reviewing other steps
- Ignoring accessibility: Screen readers and keyboard-only users must be able to use the form
- Not handling async validation: Use loading states for server-side validation
- 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.