Skip to Content
Welcome to RitoSwap's documentation!

NFT Store

The NFT store serves as the centralized state‑management solution for all Colored Key NFT data within RitoSwap. Built with Zustand, this store maintains real‑time token information, manages account‑switching scenarios, and persists critical data across browser sessions. The store acts as the single source of truth for NFT‑related UI state throughout the application.

Store Architecture

The NFT store implements a sophisticated state‑management pattern that balances reactivity, persistence, and performance. By leveraging Zustand’s lightweight architecture, the store provides immediate state updates to subscribed components while maintaining a clean, predictable API.

Core State Structure

interface NFTState { // Core NFT data hasNFT: boolean tokenId: number | null backgroundColor: string | null keyColor: string | null // UI state isLoading: boolean error: string | null // Token gate tracking hasUsedTokenGate: boolean // Wallet state currentAddress: Address | null // Account switching isSwitchingAccount: boolean previousData: { hasNFT: boolean tokenId: number | null backgroundColor: string | null keyColor: string | null } | null }

Each field serves a specific purpose in maintaining the application’s state consistency and enabling smooth user experiences across various scenarios.

State Field Purposes

FieldPurposePersistence
hasNFTIndicates current NFT ownership statusNo – Fetched fresh each session
tokenIdThe owned token’s unique identifierNo – Blockchain source of truth
backgroundColorAlgorithmically generated background colorNo – Derived from token ID
keyColorAlgorithmically generated key colorNo – Derived from token ID
isLoadingGlobal loading spinner flag for NFT‑related async operationsNo – Transient UI state
errorUI‑friendly error message from last failed operationNo – Transient UI state
hasUsedTokenGateWhether user has accessed gated contentYes – Persists across sessions
currentAddressCurrently connected wallet addressNo – Wallet connection state
isSwitchingAccountFlag set while the UI is waiting for new account dataNo – Temporary UI state
previousDataSnapshot of NFT data shown during an account switchNo – Cleared once switch completes

isLoading and error are UI‑only helpers. They reset to their default values on every page load and are never persisted.

Action Methods

The store exposes a complete set of actions for manipulating state. These actions map one‑to‑one with Zustand setters defined in nftStore.ts.

Basic State Updates

setHasNFT: (hasNFT: boolean) => void setTokenData: ( tokenId: number | null, backgroundColor: string | null, keyColor: string | null ) => void setLoading: (isLoading: boolean) => void setError: (error: string | null) => void

Token Gate Management

setHasUsedTokenGate: (hasUsed: boolean) => void

Account Management

setCurrentAddress: (address: Address | null) => void setIsSwitchingAccount: (isSwitching: boolean) => void

Account Switching Flow

startAccountSwitch: () => void completeAccountSwitch: () => void

State Reset

resetState: () => void

All Actions Reference

ActionPurpose
setHasNFTToggle ownership flag and trigger UI refresh
setTokenDataAtomically update tokenId & colors
setLoadingGlobal loading spinner control
setErrorSet/clear UI‑friendly error message
setHasUsedTokenGatePersist gate usage across sessions
setCurrentAddressTrack the connected wallet address
setIsSwitchingAccountInternal flag toggled by wallet listeners before a switch
startAccountSwitchSnapshot current data & enter switch mode
completeAccountSwitchClear snapshot & exit switch mode
resetStateClear all transient data while preserving gate usage + address

Account Switching Flow

Step 1: Detection

The application detects a wallet account change through wagmi’s account‑change events.

Step 2: State Preservation

startAccountSwitch() captures current NFT data in previousData and sets isSwitchingAccount to true.

Step 3: UI Continuity

Components use previousData to maintain visual state during the transition, preventing jarring UI changes.

Step 4: Data Refresh

Background processes fetch new account data while the UI remains stable.

Step 5: Completion

When new data arrives, setTokenData() or completeAccountSwitch() finalizes the transition.

Persistence Strategy

The store implements selective persistence using Zustand’s persist middleware. This approach balances data freshness with user experience:

persist( (set, get) => ({ // ... store implementation }), { name: 'nft-storage', partialize: (state) => ({ // Only persist token gate usage hasUsedTokenGate: state.hasUsedTokenGate, }), } )

The persistence configuration ensures that only non‑sensitive, user‑experience data persists across sessions. Token ownership and metadata always refresh from the blockchain to maintain accuracy.

Persistence Rationale

The decision to persist only hasUsedTokenGate reflects careful consideration of security and user experience:

Security – Token ownership must always reflect current blockchain state to prevent displaying incorrect ownership information after transfers or burns.

User Experience – Remembering token‑gate usage improves the experience for returning users by maintaining their progression through gated content even after browser restarts.

Performance – Minimal persistence reduces storage overhead and speeds up state hydration on application load.

Component Integration Patterns

Components integrate with the NFT store through various patterns depending on their requirements:

Read-Only Components

function NFTDisplay() { const { hasNFT, tokenId, backgroundColor, keyColor } = useNFTStore(); if (!hasNFT) return <EmptyState />; return ( <div style={{ backgroundColor }}> <KeyIcon color={keyColor} /> <span>Token #{tokenId}</span> </div> ); }

Interactive Components

function MintButton() { const { setLoading } = useNFTStore(); const { writeContract } = useWriteContract(); const handleMint = async () => { setLoading(true); try { await writeContract({ address: KEY_TOKEN_ADDRESS, abi: fullKeyTokenAbi, functionName: 'mint' }); // State updates handled by useNFTData hook } catch (error) { setLoading(false); } }; return <button onClick={handleMint}>Mint NFT</button>; }

Account-Aware Components

function NFTScreen() { const { hasNFT, backgroundColor, keyColor, isSwitchingAccount, previousData } = useNFTStore(); // Use previous data during account switches const displayData = isSwitchingAccount && previousData ? previousData : { hasNFT, backgroundColor, keyColor }; return <NFTVisualization {...displayData} />; }

State Update Coordination

The NFT store coordinates with the useNFTData hook to maintain consistency between blockchain state and UI state. This coordination follows a specific pattern:

Blockchain Query – The useNFTData hook queries the blockchain for current token ownership and metadata using wagmi’s useReadContract.

API Synchronization – The hook checks token usage status through the Token Status API, which synchronizes blockchain and database state.

Store Updates – The hook updates the NFT store with fresh data, triggering re-renders in subscribed components.

Error Handling – Failed queries update the error state while preserving the last known good state for UI stability.

This separation of concerns keeps the store focused on state management while delegating data fetching to specialized hooks.

Performance Optimization

The NFT store implements several optimizations to ensure smooth performance:

Selective Subscriptions

Zustand’s subscription model ensures components only re-render when their specific subscribed fields change:

// Only re-renders when hasNFT changes const hasNFT = useNFTStore(state => state.hasNFT); // Re-renders for any color change const colors = useNFTStore(state => ({ backgroundColor: state.backgroundColor, keyColor: state.keyColor }));

Atomic Updates

The setTokenData method updates multiple fields atomically, preventing intermediate states that could cause visual glitches:

// Single update prevents multiple re-renders setTokenData(tokenId, backgroundColor, keyColor); // Versus multiple updates (avoid this) setTokenId(tokenId); setBackgroundColor(backgroundColor); setKeyColor(keyColor);

Debounced Loading States

Components should debounce rapid loading state changes to prevent UI flashing:

const [debouncedLoading] = useDebounce(isLoading, 200);

Testing Strategies

Testing the NFT store requires understanding its interaction with external systems:

Unit Testing

Test store actions in isolation using Zustand’s testing utilities:

import { renderHook, act } from '@testing-library/react-hooks'; import { useNFTStore } from '@/app/store/nftStore'; test('account switching preserves data', () => { const { result } = renderHook(() => useNFTStore()); // Set initial state act(() => { result.current.setTokenData(1, '#FF0000', '#00FF00'); }); // Start account switch act(() => { result.current.startAccountSwitch(); }); // Verify state preservation expect(result.current.isSwitchingAccount).toBe(true); expect(result.current.previousData).toEqual({ hasNFT: true, tokenId: 1, backgroundColor: '#FF0000', keyColor: '#00FF00' }); });

Integration Testing

Test the store’s interaction with the data fetching layer:

// Mock the blockchain queries vi.mock('wagmi', () => ({ useReadContract: vi.fn(() => ({ data: [BigInt(42), true], isLoading: false })) })); // Test the complete flow test('NFT data updates store correctly', async () => { const { result } = renderHook(() => useNFTData()); await waitFor(() => { expect(useNFTStore.getState().tokenId).toBe(42); expect(useNFTStore.getState().hasNFT).toBe(true); }); });

Common Patterns and Anti‑Patterns

Use Selective Subscriptions – Subscribe only to the fields your component needs to minimize re-renders.

Coordinate Through Hooks – Let the useNFTData hook manage store updates rather than updating directly from components.

Handle Loading States – Always set loading states during async operations to provide user feedback.

Anti‑Patterns to Avoid

Direct Blockchain Queries in Components – Use the established data flow through hooks rather than querying contracts directly.

Multiple Rapid Updates – Batch related updates using setTokenData rather than calling individual setters.

Ignoring Account Switches – Always consider the isSwitchingAccount state when displaying NFT data to prevent UI flashing and data loss.

Summary

The NFT store represents a carefully designed state‑management solution that balances reactivity, persistence, and performance. Through its sophisticated account‑switching mechanism, selective persistence strategy, and clean API, it provides a robust foundation for NFT‑related features throughout RitoSwap. The store’s integration with blockchain queries through the useNFTData hook ensures that UI state remains synchronized with on‑chain reality while providing smooth, flicker‑free user experiences during account transitions and data updates.