Last Updated: 2026-05-29
A shared reference for the frontend state management architecture used across the YieldVault-RWA codebase. The purpose of this document is to clarify state ownership, data fetching boundaries, and UI synchronization patterns to ensure a consistent and maintainable developer experience.
The frontend heavily relies on React Context for global client state and React Query (@tanstack/react-query) for server state and asynchronous data fetching. There are no external global state stores like Redux or Zustand in this architecture.
- State Ownership
- Data Fetching Strategy
- UI Synchronization Patterns
- Key Contexts
- Key Hooks
- Component Architecture
- State Boundaries & Best Practices
State in the YieldVault-RWA frontend is strictly divided into three domains:
- Server State (React Query): External data that requires asynchronous fetching, caching, and background updates. Owned entirely by React Query hooks (e.g., Share Price, USDC Balance, Vault History, Transaction History).
- Global Client State (React Context): UI state and preferences that multiple disparate components need access to. Managed via Context providers (e.g., Wallet Connection/Auth, Theme, Toast Notifications, computed Vault Metrics).
- Local Component State (React State): Ephemeral UI state specific to a single component or view. Handled by
useStateand custom form hooks (e.g., Transaction Wizard steps, input amounts, active tabs).
Data fetching is exclusively handled by React Query, utilizing custom hooks to encapsulate the query keys, fetch functions, and caching policies.
The global QueryClient is configured with sensible defaults to prevent aggressive refetching and improve the user experience:
refetchOnWindowFocus: Disabled for specific queries likeuseSharePriceto avoid unnecessary RPC calls on tab switching.retry: Limited to 1 for most queries to fail fast and display graceful error states to the user.
To optimize RPC calls and Horizon API usage, queries have specific staleTime configurations based on data volatility:
- USDC Balance:
10 seconds— Balance is critical for transactions but we avoid spamming the RPC (useUsdcBalance). - Transaction History:
15 seconds— Updates frequently as users transact (useTransactionHistory). - Vault Summary:
30 seconds— Vault metrics (TVL, Deposit Cap) update relatively slowly (useVaultSummary). - Vault History:
60 seconds— Historical charting data changes infrequently (useVaultHistory).
Critical real-time metrics use a custom useQueryWithPolling wrapper. For example, useSharePrice implements a 30-second polling interval (POLLING_INTERVALS.normal) that pauses when the window is hidden or the network is offline.
Complex view state, such as data tables and filters, is synchronized with the URL search parameters.
useDataTableState: Syncs pagination (page, pageSize) and sorting state to the URL.useTransactionFilters: Syncs multi-filter criteria (date ranges, statuses, types) to the URL.- Deep Linking: Components like
VaultDashboardlisten for URL parameters (e.g.,?action=deposit&amount=100) to automatically pre-fill local state (active tab, input amount) on mount, then clean up the URL to prevent state trapping.
Instead of relying on backend endpoints for every data permutation, raw data is fetched via React Query and heavily processed client-side:
- Vault Metrics:
VaultContextconsumes rawuseVaultSummaryanduseVaultHistorydata, computes the active APY and Vault Utilization, and provides these derived metrics to all dashboard components. - Table Filtering:
TransactionHistoryfetches a raw list of transactions (max 200) and usesuseClientDataTableto handle sorting, filtering, and pagination entirely in memory.
All global providers are located in frontend/src/context/.
VaultContext
The central data hub for the vault. It wraps the useVaultSummary and useVaultHistory queries, calculates derived metrics like apy, utilization, isCapWarning, and isCapReached, and handles global API error normalization. Components use useVault() to consume these metrics without worrying about loading states individually.
AuthContext
Manages the user's wallet session state (idle, warning, expired). It tracks session duration (30 minutes) and handles warning triggers and forced expirations, ensuring the UI accurately reflects wallet security state.
ToastContext
A global notification system exposing methods like toast.success(), toast.error(), and toast.warning() for transaction feedback and system alerts.
Custom hooks in frontend/src/hooks/ encapsulate all complex logic.
Data Hooks (React Query):
useSharePrice: Fetches the current vault share price with 30s background polling.useUsdcBalance: Fetches the connected wallet's USDC balance. Enabled only when a wallet is connected.useTransactionHistory: Fetches the user's past deposits and withdrawals.useVaultSummary&useVaultHistory: Fetch global vault stats and historical data.
Mutation Hooks (React Query):
useVaultMutations: ExposesuseDepositMutationanduseWithdrawMutationfor executing Soroban contract calls. Automatically invalidates related query caches (balances, transactions) on success.
Utility Hooks:
useClientDataTable: Handles client-side pagination, sorting, and text-based filtering of arrays.useInfiniteScroll: Manages IntersectionObserver logic for loading more items in infinite-scroll views.useFeeEstimate/useSlippage: Calculates estimated transaction fees and slippage parameters for deposits and withdrawals.
- State Consumed:
VaultContext(global stats),useUsdcBalance(wallet balance),useTokenAllowance(contract approvals). - Local State: Manages the transaction wizard. Tracks
activeTab("deposit" | "withdraw"),currentStep("amount" | "review" | "result"), and form validation via a customuseFormhook. It also maintainstransactionResultfor displaying success/failure states post-mutation. - Event Listeners: Listens for global
TRIGGER_DEPOSIT/TRIGGER_WITHDRAWevents to auto-focus inputs, allowing disjointed UI elements (like the EmptyState) to orchestrate dashboard actions. - Mutations: Executes
useDepositMutationanduseWithdrawMutation, triggering localized confetti effects and toast notifications on success. It leverages the globalQueryClientto auto-invalidate balances upon completion.
- State Consumed:
useTransactionHistory(raw transaction list). - Local State: Manages
viewMode("paginated" | "infinite") and infinite scroll batch counts viauseStateanduseRef. Persists user preferences forviewModeandpageSizetolocalStorage. - URL Synchronization: Binds heavily to URL parameters using
useDataTableStateanduseTransactionFiltersto ensure the view is shareable and persists across refreshes. - Rendering: Passes raw data through
useClientDataTablefor in-memory transformations. Renders either a standard paginatedDataTableor an infinite scroll container based onviewMode.
To maintain a clean and scalable frontend architecture, adhere to the following guidelines:
- Prefer React Query over Context for Data: Do not fetch API data inside a React Context unless it requires heavy, cross-component computation (like
VaultContext). Rely on React Query hooks directly within components. - Keep Local State Local: Avoid hoisting
useStatehigher than necessary. If only a single child component needs the state, keep it encapsulated within that child. - Use the URL as the Source of Truth: For shareable states like search queries, filters, or active tabs, use the URL parameters instead of internal
useState. - Don't Duplicate Server State: Avoid copying React Query data into local
useState. Derive values directly from the query data during render. - Colocate Form State: Use the custom
useFormhook for transaction inputs and validation. Avoid storing form inputs in global contexts.
This document is the authoritative reference for frontend state management. Please ensure new features align with these established patterns to maintain codebase consistency.