Skip to content

Improve send/buy store state machine to model full flow lifecycle #933

@gudnuf

Description

@gudnuf

Problem

The send and buy stores (send-store.ts, buy-store.ts) only have two status states: idle and quoting. But the stores live across multiple routes (input → confirmation → transaction details), so the status doesn't represent where the user actually is in the flow.

This causes a concrete bug: when quoting completes, the store resets to idle before React Router navigation starts. The continue button flashes: loading → not loading → loading. There's a brief gap between the async quote completing and isNavigating becoming true.

Current fix (PR #925)

Added a local isContinuing boolean in the input components (send-input.tsx, buy-input.tsx). Loading is derived as:

loading={status === 'quoting' || isContinuing}

This works but:

  • The store status "lies" — on the confirmation page, status is idle even though the user is mid-flow
  • Back navigation leaves the store in a stale state (was success before Fix send/buy continue loading state across navigation #925, now just doesn't track at all)
  • Boolean flags tend to proliferate as more states get added

Proposed improvement

Model the full flow lifecycle in the store status:

idle → quoting → confirmation → (success → unmount)
State Meaning Where
idle Ready for input Input page
quoting Creating quote (async) Input page (loading)
confirmation Quote ready, user reviewing Confirmation page
success Payment initiated, navigating to tx details Confirmation page

Transitions

  • idle → quoting: User clicks Continue, proceedWithSend()/getBuyQuote() starts
  • quoting → confirmation: Quote created successfully. Set by useLayoutEffect in confirmation component (after view transition completes), replacing the current pattern where proceedWithSend sets idle after quoting
  • quoting → idle: Quote fails (error toast, stay on input)
  • confirmation → success: Payment mutation succeeds (already tracked by TanStack mutation status on confirmation page, but store could also reflect this)
  • On back navigation to input: useLayoutEffect on input component resets status to idle

Key design considerations

  • View transitions: We use separate routes for each step because of view transition ergonomics. The store must bridge the gap between async operations completing and navigation finishing.
  • isNavigating gap: React Router's isNavigating doesn't become true immediately when navigation is triggered programmatically — there's a tick between mutation completion and navigation start.
  • Store unmounts on completion: When navigating to /transactions/:id, the send/buy layout unmounts, destroying the store. So success is effectively terminal.
  • Loading derivation: With the full state machine, the continue button can derive loading from status === 'quoting' alone. The isContinuing local state and the ['pending', 'success'].includes(mutationStatus) pattern on confirmation could potentially be simplified.

Scope

  • Update send-store.ts status type to 'idle' | 'quoting' | 'confirmation'
  • Update buy-store.ts status type to 'idle' | 'quoting' | 'confirmation'
  • Transition to confirmation via useLayoutEffect in confirmation components
  • Transition to idle via useLayoutEffect in input components (handles back navigation)
  • Remove isContinuing local state from input components
  • Keep proceedWithSend/getBuyQuote setting quoting on start, but NOT resetting to idle on success (let confirmation component handle that transition)
  • Consider whether success state is needed or if TanStack mutation status on the confirmation page is sufficient

Context

  • Discussion: Discord thread between gudnuf and josip (2025-03-05)
  • Related PR: Fix send/buy continue loading state across navigation #925 (interim fix with isContinuing boolean)
  • The send confirmation page already uses TanStack Query mutation status for its own loading (['pending', 'success'].includes(status)) — separate from the store
  • The buy checkout page has no confirm button (payment is external via Cash App)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions