Skip to content

feat(streams): split data loading from table rendering - #1590

Merged
Jagadeeshftw merged 1 commit into
Fluxora-Org:mainfrom
retkatmun:feat/1410-streams-data-presentation-split
Aug 31, 2026
Merged

feat(streams): split data loading from table rendering#1590
Jagadeeshftw merged 1 commit into
Fluxora-Org:mainfrom
retkatmun:feat/1410-streams-data-presentation-split

Conversation

@retkatmun

Copy link
Copy Markdown
Contributor

Summary

Closes #1410

src/pages/Streams.tsx was 1,459 lines combining network loading, filter state, pagination, session recovery, ARIA announcements, and layout in one place. This PR extracts the two layers that were already partially in place but never committed or tested.


Before / After

Before
Streams.tsx 1 459 lines — data + filters + pagination + session recovery + layout all mixed
useStreamsData.ts Existed on disk but never committed; no tests
StreamsListPanel.tsx Existed on disk but never committed; no tests
Streams.test.tsx Broken — had the entire useStreamsData.ts source concatenated into it, referenced undefined symbols (RecipientStreams, writeStreamsSession, activeStream, DEFAULT_STREAMS_FILTERS)

After: both layers are committed, documented, and covered by 80 focused tests.


Data layer — useStreamsData.ts

Single hook that owns everything that touches the network or localStorage:

  • useTreasury fetch + useOptimisticStreams overlay
  • Stale optimistic-row reconciliation on mount
  • Abort-controller-based manual refetch (refetchStreams)
  • Status / search / sort filter state → visibleStreams
  • Pagination state with automatic page-clamp when filters shrink the result set
  • Summary metrics: activeStreams, monthlyOutflow, withdrawableNow, nextUnlock
  • Zero-accrual banner flags (showZeroAccrual, zeroAccrualReason)
  • Session recovery: read / write / restore / discard (scoped by wallet address)
  • Debounced live ARIA announcement via useLiveAnnouncer
  • Rollback toast edge-case tracking

Streams.tsx consumes the hook through a single const data = useStreamsData() destructure and passes everything down to the presentation layer as props.

Presentation layer — StreamsListPanel.tsx

Pure props-in component with no internal state or side effects:

  • Search input, status-filter pills with aria-pressed, sort select
  • VirtualList of stream cards via a renderStream render prop (panel stays agnostic to StreamCard internals)
  • Pagination bar wired to onPageChange / onItemsPerPageChange
  • SessionPersistenceIndicator threaded through recentlySaved

Tests — 80 total, all pass

src/pages/useStreamsData.test.ts — 41 tests (new)

All via renderHook(), zero JSX. Covers:

  • Status filter (All / Active / Paused / Completed)
  • Search by name, recipientName, id (case-insensitive, no-match)
  • Combined filter + search intersection
  • Sort by name A–Z and rate desc
  • Pagination: first page slice, page 2 slice, final partial page, page-clamp on filter shrink, itemsPerPage
  • Summary metrics: activeStreams count, monthlyOutflow sum, withdrawableNow sum, nextUnlock min date, edge cases with no Active streams
  • Zero-accrual: showZeroAccrual true/false, zeroAccrualReason rate-zero vs cliff
  • Display flags: showEmptyState, hasStreams, effectiveExpandedId fallback
  • Loading / error pass-through, refetch wiring
  • isAbortError guard (plain string → false, null → false)

src/components/Streams/StreamsListPanel.test.tsx — 25 tests (new)

Pure render tests — no hook required:

  • Title, subtitle, renderStream called once per stream
  • Empty state shown when paginatedStreams is empty; hidden when not
  • Status filter buttons present, aria-pressed reflects active filter, is-active class, onStatusFilterChange callback
  • Search input aria-label, value pre-fill, onSearchChange callback
  • Sort select options, selected value, onSortChange callback
  • Pagination component mounted for multi-page data, onPageChange callback
  • SessionPersistenceIndicator: aria-label, data-recently-saved attribute on/off
  • List aria-label, outer section element

src/pages/Streams.test.tsx — 14 tests (fixed)

Removed the accidental source-code concatenation and all undefined-symbol references. Added missing mocks (useOptimisticStreams, useWallet, lib/stellar/tx). Kept the full sortStreams pure-function suite and integration sort tests. Added a not-found-state integration test.

vitest.config.ts

useStreamsData.ts and StreamsListPanel.tsx added to the coverage include list.


What is unchanged

  • Streams.tsx public API and route behaviour are identical — same query params, loading states, error states, empty states, accessibility attributes, session recovery banner, zero-accrual banner, StreamCard expand/collapse, VirtualList, and Pagination.
  • No new dependencies. No test deletions. No threshold weakening.

CI / verification

npx vitest run src/pages/Streams.test.tsx src/pages/useStreamsData.test.ts src/components/Streams/StreamsListPanel.test.tsx

Pre-existing full-suite worker-timeout failures (i18n/__tests__, Recipient.notification) are confirmed in the baseline vitest-full-run.log and are unrelated to this PR.

)

Extract useStreamsData hook and StreamsListPanel component from the
1 459-line Streams.tsx monolith, then add focused regression tests for
both layers.

Data layer (useStreamsData.ts)
- Owns useTreasury fetch, optimistic overlay, stale-request abort
- Status/search/sort filter state and visibleStreams derivation
- Pagination state with automatic page-clamp
- Summary metrics: activeStreams, monthlyOutflow, withdrawableNow, nextUnlock
- Zero-accrual banner flags
- Session recovery read/write/restore/discard
- Live ARIA announcement debounce
- Rollback toast edge-case tracking

Presentation layer (StreamsListPanel.tsx)
- Pure props-in component: search input, status pills, sort select,
  VirtualList of StreamCards via render prop, Pagination bar,
  SessionPersistenceIndicator
- No internal async state or side effects

Tests added (80 total, all pass)
- src/pages/useStreamsData.test.ts (41 tests)
  filters, pagination, summary metrics, zero-accrual flags,
  loading/error pass-through, isAbortError guard, display flags
- src/components/Streams/StreamsListPanel.test.tsx (25 tests)
  rendering, status filter aria-pressed, search/sort callbacks,
  pagination wiring, session indicator props, accessibility roles
- src/pages/Streams.test.tsx (14 tests, fixed)
  Removed broken source-code concatenation and undefined symbol refs;
  kept sortStreams pure-function suite and integration sort tests;
  added missing mocks for useOptimisticStreams and useWallet

Coverage: useStreamsData.ts and StreamsListPanel.tsx added to
vitest.config.ts include list.

Closes Fluxora-Org#1410
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@retkatmun Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@retkatmun

Copy link
Copy Markdown
Contributor Author

kindly review and merege @Jagadeeshftw

@Jagadeeshftw
Jagadeeshftw merged commit d007a90 into Fluxora-Org:main Aug 31, 2026
1 of 2 checks passed
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.

Split Streams page data loading from table rendering

2 participants