Skip to content

refactor(dashboard): use cached useDashboard hook for snapshot data - #1382

Open
Rafiat30 wants to merge 1 commit into
LabsCrypt:mainfrom
Rafiat30:fix/use-cached-dashboard-hook
Open

refactor(dashboard): use cached useDashboard hook for snapshot data#1382
Rafiat30 wants to merge 1 commit into
LabsCrypt:mainfrom
Rafiat30:fix/use-cached-dashboard-hook

Conversation

@Rafiat30

Copy link
Copy Markdown

Closes #1244

What changed

useDashboard (frontend/src/lib/dashboard.ts) is a React Query hook — it inherits staleTime: 10_000, refetchOnWindowFocus: false, and retry: 1 from the QueryClient created in frontend/src/components/providers/query-provider.tsx — but it was exported and never called anywhere. DashboardView instead hand-rolled its own useState/useEffect fetch of the snapshot, so every mount refetched from scratch with no caching, retry, or request dedup, and switching tabs/routes away and back re-fetched the same data unnecessarily.

DashboardView now calls useDashboard(session.publicKey) and drives all snapshot state through the React Query cache:

  • Loading/error state now come from the hook's isLoading / isError / error instead of separate useState flags.
  • Optimistic local updates (topUpStreamLocally, addStreamLocally) now write through queryClient.setQueryData(dashboardQueryKey(session.publicKey), updaterFn) instead of a local setSnapshot, so the cache and the UI stay in sync.
  • SSE-triggered refresh: when a relevant stream event arrives over SSE, the component now calls queryClient.invalidateQueries({ queryKey: dashboardQueryKey(session.publicKey) }) instead of manually calling fetchDashboardData and setSnapshot. React Query handles the refetch and re-render.
  • Withdraw flow: after a successful withdrawal, the component calls the hook's refetchSnapshot() (React Query's refetch) instead of manually re-fetching and calling setSnapshot.
  • Error retry button calls refetchSnapshot() instead of a bespoke loadSnapshot callback.
  • The old hand-rolled useEffect fetch-on-mount effect and loadSnapshot callback were removed entirely — useDashboard now owns the fetch/cache lifecycle.

Because the hook's queryFn (fetchDashboardData) and queryKey (dashboardQueryKey(publicKey)) are unchanged, and the app's root layout already wraps everything in the single long-lived QueryProvider/QueryClient, remounting DashboardView (e.g. navigating away and back, or switching tabs) within the 10s staleTime window is now served from cache instead of issuing a new network request.

Files changed

Modified

  • frontend/src/components/dashboard/dashboard-view.tsx — replaced hand-rolled fetch/loading/error state with useDashboard; optimistic updates and SSE refresh now go through the query cache; withdraw/retry use refetchSnapshot.
  • frontend/src/lib/dashboard.test.ts — added a describe("useDashboard", ...) block.

New

  • frontend/src/components/dashboard/dashboard-view.test.tsx — component-level tests for DashboardView wired to a real QueryClient.

Tests added

In frontend/src/lib/dashboard.test.ts (useDashboard hook, via renderHook + QueryClientProvider):

  • Query is disabled (no fetch) when publicKey is empty.
  • Fetches and maps outgoing/incoming streams into a DashboardSnapshot (2 backend requests: sender + recipient).
  • Acceptance criterion: with a QueryClient configured with the app's real defaults (staleTime: 10_000, refetchOnWindowFocus: false, retry: 1), unmounting and remounting useDashboard with the same QueryClient within the staleTime window results in no additional fetch calls — the second mount is served from cache (fetchStatus/isFetching confirm no in-flight refetch).

In frontend/src/components/dashboard/dashboard-view.test.tsx (full DashboardView, with useStreamEvents, react-hot-toast, @/lib/soroban, @/lib/stellar, and heavy child components — wizard/modals/SSE indicator/IncomingStreams — mocked out so the test focuses on the query-cache wiring):

  • Unmounting and remounting DashboardView within the staleTime window does not issue a duplicate network fetch (mirrors the issue's acceptance criterion end-to-end through the real component).
  • Shows the loading skeleton, then renders content once useDashboard resolves.
  • Shows the error state with a retry button when the fetch fails, and clicking retry successfully refetches.

Manual test plan

  1. Run the frontend (npm run dev --workspace=frontend) against a backend with some streams for a connected wallet.
  2. Open the dashboard, open the browser Network tab, and note the two /streams requests (sender + recipient).
  3. Switch to another tab/route in the app and back to the dashboard within ~10 seconds. Confirm in the Network tab that no new /streams requests fire — the dashboard renders instantly from cache.
  4. Wait past the 10s staleTime window, switch away and back again, and confirm a fresh fetch does happen this time.
  5. Top up or create a stream — confirm the UI updates immediately via the optimistic queryClient.setQueryData write, without a network round trip.
  6. Trigger a relevant SSE event (create/top-up/withdraw/cancel/pause/resume from another session) and confirm the dashboard automatically refetches and reflects the change (via invalidateQueries).
  7. Withdraw from a stream — confirm the dashboard refetches and reflects the updated balance.
  8. Force a fetch failure (e.g. stop the backend) — confirm the error state renders with a retry button, and clicking retry re-fetches and recovers once the backend is back.

…absCrypt#1244)

DashboardView previously hand-rolled its own fetch/loading/error state
for the dashboard snapshot in a useEffect, even though useDashboard (a
React Query hook with staleTime/retry inherited from QueryProvider) was
already exported from lib/dashboard.ts and unused. This meant every
mount refetched the snapshot from scratch, with no caching, retry, or
request dedup.

DashboardView now calls useDashboard(session.publicKey) directly:
- Loading/error state derive from the hook's isLoading/isError/error.
- Local optimistic updates (topUpStreamLocally, addStreamLocally) write
  through queryClient.setQueryData(dashboardQueryKey(...)) instead of a
  local useState setter.
- The SSE-triggered refresh invalidates the query
  (queryClient.invalidateQueries) instead of manually refetching.
- The withdraw flow calls the hook's refetchSnapshot() instead of
  manually re-fetching and setting state.
- The error state's retry button calls refetchSnapshot().

Added tests proving a remount within the configured staleTime does not
trigger a duplicate network fetch (the issue's acceptance criterion),
both at the hook level (lib/dashboard.test.ts) and through the full
DashboardView component (dashboard-view.test.tsx).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Audit] Ready-made cached dashboard hook exists but isn't used, so the dashboard has no request dedup/caching

2 participants