Phase 4: Results Display & Performance Optimization
Overview
Transform raw rate data into an insightful, performant user interface. You'll build comparison tables, implement intelligent sorting and filtering, and apply React 19's performance features to ensure a smooth user experience even with multiple carriers and complex calculations.
Learning Objectives
By the end of this phase, you will:
- Create data-rich UI components with React 19 Suspense
- Implement performance optimizations using useMemo and useCallback
- Build interactive filtering and sorting features
- Design visual comparison aids and recommendations
- Handle loading and error states gracefully
- Persist and restore calculation results
Requirements
1. Results Display Components
Main Results Page Structure:
Create src/app/results/page.tsx as a client component that:
- Imports and uses React 19's Suspense component
- Uses Next.js
useSearchParamshook to access URL params - Renders a container with:
- Page title "Shipping Rate Comparison"
- Suspense boundary wrapping
RatesDisplaycomponent ResultsSkeletonLoaderas the fallback during loading
Required Components:
Create the following components in src/components/results/:
Main Components:
RatesDisplay- Container that fetches rate data (using Suspense) and conditionally renders error messages, empty state, or results tableRatesComparisonTable- Desktop-optimized table view for comparing ratesRateCard- Mobile-friendly card view for individual rates
Supporting Components:
RatesFilters- Controls for filtering carriers and sorting resultsBestValueBadge- Visual indicator for cheapest/fastest/best-value optionsResultsSkeletonLoader- Animated loading placeholderRatesErrorDisplay- Shows partial failures from carriersNoRatesFound- Empty state when no rates are available
2. Suspense Integration with use() Hook
Goal: Implement React 19's Suspense for data fetching with automatic loading states.
Review: React 19 Mastery course, Topics 8-9 - Suspense and use() hook
File to Create: src/lib/rates-api.ts
fetchRates Function:
Create async fetchRates(request: RateRequest): Promise<RateResponse> function that:
- Makes POST request to
/api/ratesendpoint - Sends
RateRequestas JSON body - Returns parsed
RateResponse - Throws error if response not ok
RatesDisplay Component:
Create src/components/results/RatesDisplay.tsx as a client component that:
- Receives a
ratesPromiseprop (Promise) - Uses React 19's
use()hook to consume the promise directly - Conditionally renders:
- Error display if response has errors
- Empty state if no rates found
- Results summary and comparison table if rates exist
Results Page with Suspense:
In src/app/results/page.tsx:
- Create the promise at the page level (outside the Suspense boundary)
- Pass the promise to RatesDisplay inside the Suspense boundary
- Suspense automatically shows fallback while promise is pending
How React 19 Suspense Works:
- Parent component creates the promise and passes it down
- Child component calls
use(promise)to read the value - If promise is pending, React suspends and shows the fallback
- When promise resolves, React re-renders with the data
- If promise rejects, the nearest Error Boundary catches it
3. Comparison Table Implementation
Goal: Build an interactive, sortable table for desktop users to compare shipping rates.
File to Create: src/components/results/RatesComparisonTable.tsx
Component Props:
rates: ShippingRate[]- Array of shipping rates to display
State Management:
Define types and use useState to track:
sortField: 'price' | 'speed' | 'carrier'- Current sort columnsortDirection: 'asc' | 'desc'- Current sort orderselectedCarriers: CarrierName[]- Array of carriers to filter by
Memoized Calculations:
Use useMemo for performance:
-
displayedRates - Filtered and sorted rates:
- Filter by selected carriers if any are selected
- Create a copy before sorting (use spread operator or
toSorted()) to avoid mutating props - Sort by current field and direction
- Dependencies: rates, sortField, sortDirection, selectedCarriers
-
bestValues - Best value recommendations:
- Handle empty rates array (return null values if empty)
- Find cheapest rate (lowest totalCost)
- Find fastest rate (earliest estimatedDeliveryDate)
- Calculate best value using scoring:
totalCost + (businessDays × 2) - Dependencies: rates
Event Handlers:
Use useCallback for handlers passed to child components:
handleSort(field)- Toggles direction if same field, otherwise sets new field with 'asc'
Table Structure:
Render HTML table with:
- Header row with sortable columns (carrier, service, price, delivery, features)
- Clickable headers that call
handleSort() - Visual sort indicators (up/down arrows) showing current sort field/direction
- Body rows mapping over
displayedRates:- Carrier logo and name
- Service details (name and code)
- Total cost with expandable fee breakdown
- Best value badge for cheapest rate
- Estimated delivery date and business days count
- Best value badge for fastest rate
- Features list (chips or comma-separated)
Helper Components to Create:
SortIcon- Displays up/down arrow based on directionCarrierLogo- Displays carrier logo imageFeeBreakdown- Expandable list showing base rate + feesFeaturesList- Displays rate features
Styling: Use Tailwind CSS with hover states, proper spacing, and responsive overflow handling.
4. Mobile-Responsive Card View
Goal: Create mobile-friendly card layout for rate comparison.
File to Create: src/components/results/RateCard.tsx
Component Requirements:
Create RateCard component that:
- Accepts
rate(ShippingRate) andisBestValue(boolean) props - Uses
useStateto track expanded state for fee breakdown
Card Structure:
Design a card with:
-
Header section:
- Carrier logo (large size for mobile)
- Carrier name and service name
- Best value badge if applicable
-
Two-column grid displaying:
- Left: Total cost (large, bold, green text)
- Right: Delivery date and business days count
-
Expandable fee breakdown:
- Toggle button showing "Show/Hide fee breakdown"
- When expanded, display:
- Base rate
- List of all additional fees with amounts
- Use collapsible div with border-top
-
Features list (compact format)
-
Action button - Full-width "Select This Rate" button
Styling:
- Rounded card with shadow
- Border that highlights on hover (transparent → blue)
- Proper spacing and padding for touch targets
- Responsive grid layout
- Smooth transitions for interactions
5. Filters and Sorting Controls
File to Create: src/components/results/RatesFilters.tsx
Component Requirements:
Create RatesFilters component that:
- Accepts
onFilterChangeandonSortChangecallback props - Renders in a responsive grid (3 columns on desktop, stacked on mobile)
Filter Controls:
-
Carrier Filter:
- Checkbox group for selecting carriers
- Component:
CarrierCheckboxGroup - Allows multiple carrier selection
-
Sort By Dropdown:
- Select element with options:
- "Lowest Price" (value: 'price')
- "Fastest Delivery" (value: 'speed')
- "Carrier Name" (value: 'carrier')
- Triggers
onSortChangewith selected value
- Select element with options:
-
Delivery Speed Filter:
- Component:
ServiceSpeedFilter - Filters by service speed tiers
- Component:
Styling:
- Light gray background container
- Rounded corners with padding
- Proper label styling
- Responsive grid layout
6. Performance Optimization
Goal: Optimize React re-renders and expensive calculations.
Review: React 19 Mastery course, Topic 10 - Performance Optimization
Optimization Techniques to Implement:
-
useMemo for Expensive Calculations:
- Memoize sorted/filtered rates - recalculate only when rates, sortField, sortDirection, or filters change
- Memoize best value recommendations - recalculate only when rates array changes
- Memoize value score calculations
-
useCallback for Event Handlers:
- Memoize
handleCarrierTogglecallback to prevent child re-renders - Memoize
handleSortChangecallback - Memoize all event handlers passed as props to child components
- Memoize
-
React.memo for Components:
- Wrap
RateCardinReact.memowith custom comparison function - Only re-render if rate.id or isBestValue props change
- Prevents unnecessary re-renders when parent updates
- Wrap
-
Avoid Inline Object/Array Creation:
- Don't create new objects or arrays in render
- Extract to constants or useMemo when needed
Measuring Performance:
- Use React DevTools Profiler to identify slow components
- Monitor re-render counts
- Optimize components that render frequently
7. Results Persistence
Goal: Cache rate results to improve UX and reduce API calls.
File to Create: src/lib/results-storage.ts
Implementation Requirements:
-
Define Constants:
- Storage key: 'shipping-rate-results'
- TTL (time to live): 30 minutes (1000 × 60 × 30 milliseconds)
-
Create StoredResults Interface:
- response: RateResponse
- request: RateRequest
- timestamp: number (milliseconds)
-
Implement Functions:
saveResults(request, response):
- Create StoredResults object with current timestamp
- Serialize to JSON
- Save to localStorage with defined key
loadResults():
- Retrieve from localStorage
- Return null if not found
- Parse JSON to StoredResults
- Check if expired (current time - timestamp > TTL)
- If expired, call clearResults() and return null
- Otherwise, return the stored data
clearResults():
- Remove item from localStorage
Usage: Call saveResults() after successful rate fetch, call loadResults() on page load to check for cached results.
Deliverables
By the end of Phase 4, you must have:
- Responsive results display (desktop table + mobile cards)
- React 19 Suspense integration with use() hook
- Sorting and filtering functionality
- Best value recommendations with visual badges
- Fee breakdown display (collapsible)
- Performance optimizations (useMemo, useCallback, memo)
- Results persistence to localStorage
- Loading skeletons and error states
- Accessibility-compliant UI (keyboard navigation, ARIA labels)
- Works flawlessly on mobile (320px+) and desktop
Validation Checklist
- Results display correctly on all screen sizes
- Suspense shows loading state during fetch
- Sorting and filtering update instantly
- Best value badges appear on correct rates
- Fee breakdown shows all additional costs
- No unnecessary re-renders (check with React DevTools Profiler)
- Results persist across page refreshes
- Keyboard navigation works for all interactive elements
- Screen reader announces important changes
Testing Requirements
Component Tests:
Create test file: src/components/results/__tests__/RatesComparisonTable.test.tsx
Write tests to verify:
- Sorting by price works correctly (ascending and descending)
- Sorting by delivery date works correctly
- Sorting by carrier name works alphabetically
- Carrier filtering removes unwanted carriers from display
- Best value identification correctly finds cheapest and fastest rates
- Value score calculation balances price and speed appropriately
Performance Tests:
Create test file: src/components/results/__tests__/Performance.test.tsx
Write tests to verify:
- Components memoized with React.memo don't re-render when props unchanged
- useMemo dependencies work correctly (recalculates only when needed)
- useCallback prevents function recreation on every render
- Profiler measurements show acceptable render times
Resources
- Course: React 19 Mastery (Topics 8, 9, 10)
- Course: JavaScript ES6+ Foundations (Topic 4: Arrays)
- React Suspense Documentation
- React Performance Optimization
Estimated Time
- Comparison table: 3 hours
- Mobile cards: 2 hours
- Filters and sorting: 2 hours
- Suspense integration: 2 hours
- Performance optimization: 2 hours
- Testing: 2 hours
- Total: 13 hours
Next Steps
Proceed to Phase 5: Testing, Optimization & Deployment for comprehensive testing, final optimizations, and deployment preparation.