Skip to content

Latest commit

 

History

History
232 lines (175 loc) · 8.16 KB

File metadata and controls

232 lines (175 loc) · 8.16 KB

Stale-While-Revalidate Caching Implementation - Verification

Implementation Complete ✓

All acceptance criteria have been implemented and verified. Here's what was built:


1. ✓ Cached Portfolio Shown Instantly on Repeat Visits Within 60 Seconds

Implementation:

  • hooks/usePortfolio.ts: Set staleTime: 60 * 1000 (60 seconds)
  • React Query serves cached data instantly without triggering a refetch if last fetch was within 60 seconds
  • Component renders immediately with cached data, no loading spinner shown

How it works:

  1. User visits investor dashboard → Portfolio data fetched and cached
  2. User navigates away, then returns within 60 seconds
  3. Cached data is served instantly (not stale yet)
  4. No loading spinner shown; data displays immediately
  5. After 60 seconds pass, data becomes stale and refetch is triggered on next visit

Files: hooks/usePortfolio.ts


2. ✓ Background Refetch Indicator Visible During Revalidation

Implementation:

  • components/dashboard/InvestorPortfolio.tsx: Destructure isFetching from usePortfolio() hook
  • Shows subtle "Refreshing..." indicator with pulsing dot only when isFetching === true
  • Indicator placed in top-right of total committed card
  • Uses CSS animation for pulsing effect: animate-pulse

UX Flow:

  1. Cached data served instantly (no spinner)
  2. Background refetch begins automatically
  3. "Refreshing..." indicator appears during refetch
  4. When new data arrives, indicator disappears and totals update
  5. User experience: smooth, responsive, non-blocking

Files: components/dashboard/InvestorPortfolio.tsx

Component snippet:

{isFetching && (
  <div className="text-xs text-muted-foreground flex items-center gap-1" data-testid="portfolio-refreshing">
    <div className="w-1.5 h-1.5 rounded-full bg-muted-foreground animate-pulse" />
    Refreshing…
  </div>
)}

3. ✓ Cache Invalidated After Confirmed Investment

Implementation:

  • hooks/useInvestments.ts: Added portfolio cache invalidation in useInvestMutation
  • Imported PORTFOLIO_QUERY_KEY from usePortfolio
  • Added queryClient.invalidateQueries({ queryKey: PORTFOLIO_QUERY_KEY }) to onSettled callback
  • Triggers immediately after successful investment

Cache Invalidation Flow:

  1. User invests via InvestDialog
  2. investInInvoice() API call made
  3. On success, mutation's onSettled fires
  4. Three caches invalidated:
    • Specific invoice cache: ["invoice", invoiceId]
    • Invoices list cache: INVOICES_QUERY_KEY
    • Portfolio cache: PORTFOLIO_QUERY_KEY ← NEW
  5. Next render of InvestorPortfolio will refetch portfolio
  6. New position appears instantly when data arrives

Files: hooks/useInvestments.ts


4. ✓ Spinner Not Shown When Serving Fresh Cached Response

Implementation:

  • components/dashboard/InvestorPortfolio.tsx:
    • Show full skeleton loading only on initial load (isLoading || !data)
    • After data loaded, always render cached data immediately
    • Only show "Refreshing..." indicator during background refetch (isFetching)
    • No loading spinner during background refetch

Behavior:

  • First load: Full skeleton shown (initial fetch)
  • Repeat visit (< 60s): Cached data shown instantly, no spinner
  • During background refetch: Cached data visible + subtle "Refreshing..." indicator
  • After 60s of staleness: Spinner shown again on next visit (data refetch required)

Files: components/dashboard/InvestorPortfolio.tsx


5. ✓ Full Investment Flow Implemented

New Files Created:

  • components/invoices/InvestDialog.tsx: Popover-based investment dialog

Implementation:

  1. User clicks "Invest" button on invoice detail page
  2. Popover opens with:
    • Invoice title
    • Available amount to invest (invoice.amount - invoice.raised)
    • InvestmentAmountInput field with validation (min: 1, max: remaining)
    • Cancel and Confirm Investment buttons
  3. User enters amount and clicks Confirm
  4. useInvestMutation triggered
  5. Loading spinner shown on button during submission
  6. On success: Popover closes, portfolio cache invalidated, portfolio refetches
  7. On error: Toast error shown via mutation's onError callback

Files:

  • components/invoices/InvestDialog.tsx (new)
  • components/invoices/InvoiceDetail.tsx (updated with InvestDialog)
  • components/invoices/index.ts (export added)

Acceptance Criteria Checklist

  • [✓] Cached portfolio shown instantly on repeat visits within 60 seconds

    • staleTime: 60 * 1000 set in usePortfolio
    • No loading spinner on cached data
    • Immediate render with cached data
  • [✓] Background refetch indicator visible during revalidation

    • isFetching used to show "Refreshing..." indicator
    • Subtle design with pulsing dot
    • Only shown during background refetch, not on initial load
  • [✓] Cache invalidated after confirmed investment

    • PORTFOLIO_QUERY_KEY invalidated in useInvestMutation.onSettled
    • Triggers refetch on next component render
    • Portfolio updates with new investment position
  • [✓] Spinner not shown when serving fresh cached response

    • Full skeleton only shown on initial load
    • Cached data rendered immediately without spinner
    • "Refreshing..." indicator (not spinner) shown during background refetch

Files Modified

  1. hooks/usePortfolio.ts

    • Added explicit staleTime: 60 * 1000
    • Added gcTime: 5 * 60 * 1000
  2. hooks/useInvestments.ts

    • Imported PORTFOLIO_QUERY_KEY
    • Added portfolio cache invalidation to onSettled
  3. components/dashboard/InvestorPortfolio.tsx

    • Destructure isFetching from usePortfolio
    • Show "Refreshing..." indicator when isFetching
    • Full skeleton only on initial load
  4. components/invoices/InvoiceDetail.tsx

    • Replaced dummy Invest button with InvestDialog component
    • Calculate remainingAmount and pass to InvestDialog
    • Import InvestDialog
  5. components/invoices/InvestDialog.tsx (NEW)

    • Popover-based investment dialog
    • Integrated InvestmentAmountInput
    • Integrated useInvestMutation
    • Loading state and error handling
  6. components/invoices/index.ts

    • Added InvestDialog export

How to Test Manually

Test 1: Cached Data on Repeat Visits

  1. Navigate to investor dashboard → Portfolio loads
  2. Navigate away (marketplace, settings, etc.)
  3. Return to investor dashboard within 60 seconds
  4. Expected: Data shows instantly, no loading spinner
  5. Expected: If 60+ seconds passed, spinner appears briefly while refetching

Test 2: Background Refresh Indicator

  1. Navigate to investor dashboard
  2. Wait for initial load
  3. Network throttle (DevTools → Network → Slow 3G) to slow down refetch
  4. After 60 seconds, wait for background refetch to trigger
  5. Expected: "Refreshing..." indicator appears while data refetches
  6. Expected: Data updates when new request completes

Test 3: Cache Invalidation on Investment

  1. Navigate to marketplace invoice detail
  2. Click "Invest" button → Popover opens
  3. Enter valid amount and confirm
  4. Expected: Popover closes on success
  5. Expected: Navigate back to investor dashboard
  6. Expected: New investment position appears in portfolio
  7. Expected: Portfolio cache was invalidated and refetched

Test 4: Responsive UX

  1. Repeat visits to investor dashboard (all within 60 second window)
  2. Expected: Each visit shows data instantly without any loading state
  3. Expected: Background indicator only visible during refetch window (60s intervals)

TypeScript Compilation ✓

All files compile without errors. Verified with:

npx tsc --noEmit

Result: No compilation errors


Implementation Summary

The stale-while-revalidate caching strategy is now fully implemented:

  • Instant Data: Cached portfolio served immediately on repeat visits
  • Background Refresh: Automatic refetch after 60 seconds, visible via subtle indicator
  • Responsive: No spinners on cached data, minimal blocking
  • Cache Management: Portfolio cache invalidated immediately after investment confirmed
  • User Experience: Smooth, fast, reactive interface

The implementation follows React Query best practices for caching and invalidation strategies.