ButtonSection
The ButtonSection component serves as the primary action interface for all NFT-related operations in RitoSwap. This intelligent component adapts its presentation based on wallet connection status, NFT ownership, and token gate usage, providing users with contextually appropriate actions at each stage of their journey. Through sophisticated state management and transaction orchestration, it transforms complex blockchain operations into simple button clicks while maintaining comprehensive error handling and user feedback.
Component Architecture
ButtonSection implements a reactive architecture that continuously adapts to changing blockchain state while delegating transaction orchestration to the useMintBurn hook. useMintBurn coordinates wagmi-based contract writes, network checking, deeplinking, and notifications; ButtonSection focuses on rendering the correct actions for each state, managing UI transitions and loading text, wiring ProcessingModal, and invoking refresh callbacks when transactions complete or are reset.
State-Based UI Adaptation
The component renders different interfaces based on four primary states:
| State | Conditions | UI Elements |
|---|---|---|
| Not Connected | !isConnected | ConnectWrapper (wallet connection button) |
| No NFT | isConnected && !hasNFT | Mint NFT button |
| Unused NFT | isConnected && hasNFT && !hasUsedTokenGate | Token Gate link + Burn button |
| Used NFT | isConnected && hasNFT && hasUsedTokenGate | Burn button only |
This adaptive approach ensures users only see relevant actions, reducing cognitive load and preventing invalid operations.
Interactive Demo
Use the dropdown to change states
Props and Interface
ButtonSection exposes an optional refresh callback used to re-query NFT data after successful transactions:
type ButtonSectionProps = {
onRefresh?: () => Promise<void> | void
}
export default function ButtonSection({ onRefresh }: ButtonSectionProps) {
// UI logic only; blockchain orchestration delegated to hooks
}In the app, MintPageWrapper provides onRefresh by passing useNFTData().forceRefresh.
Using useMintBurn
ButtonSection delegates blockchain concerns to the useMintBurn hook, which handles:
- Network validation with the current chain
- Mobile wallet deeplinking on write actions
- Transaction receipt tracking and success deduplication
- Error formatting and notification dispatch
Minimal consumption pattern in the UI:
const { mint, burn, isProcessing, mintHash, burnHash } = useMintBurn({
onMintSuccess: async () => {
// Prevent stale processing text, then refresh UI data
setBlockProcessingText(true)
await onRefresh?.()
setLoading(false)
},
onBurnSuccess: async () => {
setBlockProcessingText(true)
await onRefresh?.()
setLoading(false)
},
})
// Event handlers
const handleMint = () => { setBlockProcessingText(false); setLoading(true); mint() }
const handleBurn = () => { setBlockProcessingText(false); setLoading(true); burn(tokenId) }Network Validation
Network checks run inside useMintBurn, preventing wrong-chain writes without the UI needing to wrap actions. This keeps ButtonSection focused on rendering and state transitions.
Mobile Wallet Integration
Mobile deeplinking is triggered by useMintBurn immediately after write actions when a WalletConnect URI is available, not by ButtonSection itself. See the dedicated guide in docs/content/dapp/wc-deeplinking.mdx for architecture and behavior details.
Success Handling
Success deduplication, notification timing, and hook resets are implemented in useMintBurn. ButtonSection handles the UI side-effects only:
const handleMintSuccess = async () => {
setBlockProcessingText(true)
await onRefresh?.()
setLoading(false)
}Success Flow
Step 1: Transaction Confirmation
Wait for blockchain confirmation via useWaitForTransactionReceipt.
Step 2: User Notification
useMintBurn dispatches notifications (toast and optional browser) through the notifications facade.
Step 3: State Refresh Delay
Wait 2 seconds to ensure blockchain state has propagated to RPC nodes.
Step 4: Force Data Refresh
Call onRefresh() (provided by MintPageWrapper) to update NFT ownership data from blockchain.
Step 5: Reset Transaction State
Clear loading state and reset wagmi hooks for future transactions.
Error Handling
Errors are formatted by formatMintError / formatBurnError and surfaced via the notifications system, then the hook resets its state. The UI does not manually show toasts for these cases, avoiding duplicates.
See dapp/app/lib/client/mint.client.ts and docs/content/dapp/notifications.mdx for details.
ProcessingModal Integration with Transaction Hash Passing
During transactions, ButtonSection displays the ProcessingModal to guide users through the transaction process. A key feature of this integration is the ability to pass transaction hashes to the modal, enabling users to track their transactions on block explorers:
// Mint-only state
<ProcessingModal
isVisible={isProcessing}
onCancel={handleModalCancel}
transactionHash={mintHash}
/>
// Burn-related states
<ProcessingModal
isVisible={isProcessing}
onCancel={handleModalCancel}
transactionHash={burnHash}
/>Transaction Hash Management
useMintBurn provides isProcessing, mintHash, and burnHash. ButtonSection passes mintHash in the mint-only state and burnHash in burn-related states. Hashes are available as soon as the write is dispatched.
Purpose of Transaction Hash Passing
The transaction hash serves multiple important purposes in the user experience. It provides transparency by allowing users to see their transaction on a block explorer while waiting for confirmation. It enables debugging in development environments where Blockscout provides detailed transaction information. It offers reassurance to users who can verify their transaction is being processed by the network. Finally, it maintains context by keeping users informed without requiring them to leave the application.
The transaction hash is passed as soon as it’s available from the blockchain, typically within seconds of initiating the transaction. This immediate feedback transforms an opaque waiting period into a transparent process where users can track progress in real-time.
Modal Cancel Functionality
The modal’s cancel button allows users to reset component state if transactions become stuck:
const handleModalCancel = async () => {
resetAll() // resets both mint and burn states from the hook
setLoading(false)
setBlockProcessingText(true)
await onRefresh?.()
}The cancel button doesn’t actually cancel blockchain transactions - it only resets the UI state. Users must still clear pending transactions in their wallet if needed. The transaction will continue processing on the blockchain regardless of UI state.
Animation and Styling
ButtonSection implements sophisticated animations for smooth state transitions:
Button State Animations
.mintButton.processing::before,
.burnButton.processing::before {
content: '';
position: absolute;
animation: waveSlide 2s ease-in-out infinite;
}The wave animation provides visual feedback during transaction processing without being distracting.
Transition Effects
.container {
opacity: 1;
transition: opacity 0.3s ease-in-out;
}
.container.transitioning {
opacity: 0;
}Fade transitions between states prevent jarring button swaps.
Hydration Safety
The component implements careful hydration handling to prevent SSR mismatches:
const [isHydrated, setIsHydrated] = useState(false)
useEffect(() => {
const timer = setTimeout(() => setIsHydrated(true), 50)
return () => clearTimeout(timer)
}, [])
if (!isHydrated || renderState === 'loading') {
return (
<div className={styles.container} role="status" aria-live="polite">
<button
className={styles.loadingButton}
disabled
aria-label="Loading NFT actions"
>
Loading...
</button>
</div>
)
}This approach ensures consistent server and client rendering while providing immediate visual feedback.
Account Switching UX
To avoid flicker and stale states when users switch accounts, the component:
- Freezes UI transitions while
isSwitchingAccountis true. - Uses a
blockProcessingTextguard to prevent showing “Processing…” after a state swap.
This creates a smoother UX around account changes without confusing transient labels.
Accessibility
ButtonSection applies a consistent accessibility layer:
- Uses
role="status"andaria-live="polite"during loading states. - Groups actions with
role="group"and descriptivearia-labels. - Applies
aria-busyto buttons while processing.
These patterns improve screen reader clarity during asynchronous interactions.
Store Integration
ButtonSection consumes only the state it needs and delegates blockchain orchestration:
| Store/Hook | Data Used | Purpose |
|---|---|---|
| useAccount | isConnected | Determines if wallet is connected |
| NFT Store | hasNFT, hasUsedTokenGate, tokenId, setLoading, isLoading, isSwitchingAccount | NFT ownership state and loading management |
| useMintBurn | mint, burn, isProcessing, mintHash, burnHash, resetAll | All blockchain write orchestration and notifications |
| onRefresh (prop) | onRefresh() provided by MintPageWrapper | Force data refresh after tx success or cancel |
Notification Integration
Notifications (toast and optional browser) are dispatched by the hook via a shared facade; the component does not emit its own success/error toasts for mint/burn.
See docs/content/dapp/notifications.mdx for configuration and behavior.
Performance Optimizations
ButtonSection implements several performance optimizations:
Hydration Timer Cleanup - The initial hydration timeout is properly cleaned up to prevent memory leaks during component unmount.
Conditional Rendering - Components only render when in appropriate states, reducing React reconciliation work.
Transaction Deduplication - Implemented in useMintBurn; ButtonSection remains stateless regarding tx IDs.
Store Subscriptions - Components subscribe only to the specific store fields they need, minimizing re-renders when unrelated state changes.
Responsive Design
The component adapts its layout for mobile devices:
@media (max-width: 768px) {
.container {
flex-direction: column;
gap: 0.8rem;
min-height: 140px; /* Taller for vertical layout */
}
.mintButton,
.gateButton,
.burnButton {
width: 100%;
max-width: 300px;
}
}Buttons stack vertically on mobile while maintaining appropriate touch targets.
Testing Strategies
When testing ButtonSection, prefer mocking useMintBurn for UI behavior and keep wagmi-level contract mocks in hook unit tests. Key scenarios:
- State transitions across connection and ownership states
- Processing state and aria attributes while
isProcessingis true - Passing the correct
transactionHashtoProcessingModal - Cancel behavior:
resetAll(),onRefresh(), and processing text guard
Common Integration Patterns
ButtonSection is typically placed below the NFT visualization:
function MintInterface() {
return (
<>
<TokenStatus />
<NFTScreen />
<ButtonSection /> {/* In app, MintPageWrapper supplies onRefresh */}
</>
)
}This arrangement creates a natural flow from status to visualization to actions.
Customization Options
The component can be extended for additional functionality:
Additional Actions
New buttons can be added for extended functionality:
if (hasNFT && !isListed) {
return <button onClick={handleList}>List on Marketplace</button>
}Custom Contract Functions
The pattern can be extended to call other contract functions:
const handleTransfer = () => {
executeWithNetworkCheck(() => {
transfer({
address: KEY_TOKEN_ADDRESS,
abi: fullKeyTokenAbi,
functionName: 'transferFrom',
args: [address, recipientAddress, tokenId]
})
})
}Alternative Styling
The modular CSS allows complete visual customization while maintaining functionality.
Best Practices
When working with ButtonSection or similar transaction components:
Always Validate Network - Handled by useMintBurn to prevent wrong-chain transactions.
Provide Clear Feedback - Use both toast and modal feedback for transaction states, including transaction hashes for transparency.
Handle All Error Cases - Implement error handling for every possible failure scenario.
Test Mobile Flows - Always test wallet deep linking on actual mobile devices.
Respect Processing States - Disable buttons during transactions to prevent double-spending attempts.
Pass Transaction Context - Always forward transaction hashes to feedback components for user transparency.
Troubleshooting Guide
| Issue | Common Cause | Solution |
|---|---|---|
| Buttons don’t appear | Hydration mismatch | Ensure hydration safety logic |
| Transaction won’t complete | Wrong network | Check network validation |
| Multiple success toasts | Effect re-runs | Implement hash deduplication |
| State doesn’t update after mint | Insufficient delay | Ensure onRefresh() is wired; allow brief propagation delay |
| No explorer link in modal | Hash not passed | Ensure transactionHash prop is set |
| UI flicker on account switch | State recompute during switch | Freeze UI while isSwitchingAccount is true |
| Stuck “Processing…” label | Render swap mid-transaction | Use blockProcessingText when transitions occur |
Faucet Link (Sepolia)
On Sepolia, ButtonSection renders a faucet link to help users obtain test ETH. The link is gated by the active chain check and appears below the action area.
Cross-References
- Hook:
dapp/app/hooks/useMintBurn.ts:1 - Client helpers:
dapp/app/lib/client/mint.client.ts:1 - Wrapper:
dapp/app/mint/components/MintPageWrapper.tsx:15 - Deeplinking Guide:
docs/content/dapp/wc-deeplinking.mdx - Notifications:
docs/content/dapp/notifications.mdx
Summary
ButtonSection focuses on adaptive UI, accessibility, modal integration, and refresh actions. The useMintBurn hook owns blockchain writes, network validation, deeplinking, notifications, and success/error lifecycles. This separation keeps the UI simple and resilient while maintaining a polished transaction experience.