You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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)
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)
Problem
The send and buy stores (
send-store.ts,buy-store.ts) only have two status states:idleandquoting. 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
idlebefore React Router navigation starts. The continue button flashes: loading → not loading → loading. There's a brief gap between the async quote completing andisNavigatingbecomingtrue.Current fix (PR #925)
Added a local
isContinuingboolean in the input components (send-input.tsx,buy-input.tsx). Loading is derived as:This works but:
idleeven though the user is mid-flowsuccessbefore Fix send/buy continue loading state across navigation #925, now just doesn't track at all)Proposed improvement
Model the full flow lifecycle in the store status:
idlequotingconfirmationsuccessTransitions
idle → quoting: User clicks Continue,proceedWithSend()/getBuyQuote()startsquoting → confirmation: Quote created successfully. Set byuseLayoutEffectin confirmation component (after view transition completes), replacing the current pattern whereproceedWithSendsetsidleafter quotingquoting → 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)useLayoutEffecton input component resets status toidleKey design considerations
isNavigatinggap: React Router'sisNavigatingdoesn't becometrueimmediately when navigation is triggered programmatically — there's a tick between mutation completion and navigation start./transactions/:id, the send/buy layout unmounts, destroying the store. Sosuccessis effectively terminal.status === 'quoting'alone. TheisContinuinglocal state and the['pending', 'success'].includes(mutationStatus)pattern on confirmation could potentially be simplified.Scope
send-store.tsstatus type to'idle' | 'quoting' | 'confirmation'buy-store.tsstatus type to'idle' | 'quoting' | 'confirmation'confirmationviauseLayoutEffectin confirmation componentsidleviauseLayoutEffectin input components (handles back navigation)isContinuinglocal state from input componentsproceedWithSend/getBuyQuotesettingquotingon start, but NOT resetting toidleon success (let confirmation component handle that transition)successstate is needed or if TanStack mutation status on the confirmation page is sufficientContext
isContinuingboolean)['pending', 'success'].includes(status)) — separate from the store