Skip to content

Conversation

@andrepimenta
Copy link
Member

@andrepimenta andrepimenta commented Nov 11, 2025

Description

This PR significantly improves the loading experience across the Predict feature by implementing skeleton loaders in place of generic loading indicators. Skeleton loaders provide visual placeholders that mimic the actual content structure, reducing perceived loading time and creating a smoother user experience.

CHANGELOG entry: null

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-11-11.at.12.59.33.mov

What Changed

Market List

  • Created PredictMarketSkeleton component to show placeholder cards during market list loading
  • Replaced generic loading spinner with realistic market card skeletons
  • Applied to both initial load and pagination footer

Market Details Page

  • Created PredictDetailsHeaderSkeleton for header section (back arrow, avatar, title)
  • Created PredictDetailsContentSkeleton for outcome options area
  • Created PredictDetailsButtonsSkeleton for action buttons (Buy Yes/No)
  • Header renders immediately with skeleton if route params are missing
  • Content and buttons show skeletons only during initial market data fetch
  • Chart component handles its own loading state independently

Home Screen - Positions Tab

  • Added inline skeleton for "Available Balance" value in PredictPositionsHeader
  • Added inline skeleton for "Unrealized P&L" value in PredictPositionsHeader
  • Removed early return to ensure card always renders during loading
  • Hidden arrow icon during balance loading state
  • Created PredictPositionSkeleton component for individual position items
  • Replaced large ActivityIndicator with 4 skeleton position cards in PredictPositions

Buy/Trade Preview Screen

  • Added smart skeleton loader for "To win" value
  • Skeleton only appears when user changes input amount (not during auto-refresh)
  • Uses previousValueRef to track actual value changes
  • Automatically clears after calculation completes
  • Prevents annoying skeleton flashing from background 1s auto-refresh

Why These Changes

  • Improved Perceived Performance: Skeleton loaders reduce perceived loading time by providing visual feedback that content is coming
  • Consistent UX Pattern: Matches modern mobile app patterns (Facebook, Instagram, etc.)
  • Better User Feedback: Shows the structure of content before it loads, setting expectations
  • Reduced Jarring Transitions: Smooth visual transition from skeleton to content vs. spinner to content
  • Context Preservation: Users can see where content will appear and what type of content to expect

Technical Approach

  1. Component Structure: Each skeleton component mirrors the layout and dimensions of its real content counterpart
  2. Modular Design: Split complex skeletons into reusable sub-components (Header, Content, Buttons)
  3. Conditional Rendering: Smart logic to show skeletons only during appropriate loading states
  4. Independent Loading States: Different sections load independently (e.g., chart vs. market data)
  5. Smart Debouncing: Buy preview skeleton uses value change detection + debouncing to avoid flash

Design Alignment

All skeleton implementations were created based on Figma SVG designs provided by the design team, ensuring pixel-perfect alignment with design specifications.

Changelog

Added

  • Added skeleton loader component for prediction market cards in feed
  • Added skeleton loaders for market details page (header, content, action buttons)
  • Added skeleton loaders for Available Balance and Unrealized P&L on home screen
  • Added skeleton loader component for position items in positions list
  • Added smart skeleton loader for "To win" value in buy preview screen

Changed

  • Replaced generic loading spinner with skeleton cards in market list
  • Replaced large activity indicator with position card skeletons in positions list
  • Updated positions header to always render main card with inline value skeletons during loading

Related Issues

Refs: [Add issue number if applicable]

Manual Testing Steps

Market List Skeleton

GIVEN I am on the Predict tab
WHEN the market list is loading for the first time
THEN I should see 4 skeleton market cards with avatar, title, content area, and footer
AND the skeletons should smoothly transition to real market cards once loaded

WHEN I scroll to the bottom of the market list
AND more markets are being fetched
THEN I should see 2 skeleton cards at the bottom
AND they should transition to real market cards once loaded

Market Details Skeleton

GIVEN I tap on a prediction market from the list
WHEN the market details page is loading
AND the title exists in route params
THEN I should see the header with real back arrow, avatar, and title immediately
AND I should see skeleton content for outcome options
AND I should see skeleton buttons at the bottom

WHEN the market data finishes loading
THEN the skeleton content should transition to real outcome options
AND the skeleton buttons should transition to real "Buy Yes" and "Buy No" buttons

GIVEN I tap on a market with no title in route params
WHEN the market details page loads
THEN I should see a skeleton header with functional back arrow
AND the avatar and title should be skeleton placeholders
AND the skeleton header should transition to real content once market data loads

Home Screen Positions Skeleton

GIVEN I am on the Predict home screen
WHEN my balance and unrealized P&L are loading
THEN I should see the main card with skeleton placeholders for both values
AND the arrow icon next to Available Balance should be hidden
AND the skeletons should smoothly transition to real values once loaded

WHEN my positions list is loading
THEN I should see 4 skeleton position cards
AND each should show avatar, title lines, and value placeholders
AND they should transition to real position items once loaded

WHEN I have no positions and no balance data
AND nothing is loading
THEN the positions header card should not render at all

Buy Preview Skeleton

GIVEN I am on the Buy/Trade preview screen
WHEN I type in a new amount value
THEN I should see the "To win" label remain visible
AND the value should show as a skeleton while calculating
AND the skeleton should disappear once calculation completes (max 500ms)

WHEN the preview auto-refreshes every 1 second
THEN the skeleton should NOT flash or appear
AND the "To win" value should update silently in the background

WHEN I clear the input to 0
THEN the skeleton state should reset
AND no skeleton should show for the "To win" value

Screenshots/Recordings

Pre-merge Checklist

  • Manually tested all skeleton loader scenarios
  • All unit tests pass (yarn jest app/components/UI/Predict)
  • No new linter errors
  • No TypeScript errors
  • Screenshots/recordings added (UI changes)
  • Follows coding guidelines and design system rules
  • PR template completed
  • Assigned to myself
  • Team label added
  • Branch follows naming convention

Components Created

New Files

  1. app/components/UI/Predict/components/PredictMarketSkeleton/

    • PredictMarketSkeleton.tsx - Skeleton for market list cards
    • PredictMarketSkeleton.test.tsx - Unit tests
    • index.ts - Barrel export
  2. app/components/UI/Predict/components/PredictDetailsHeaderSkeleton/

    • PredictDetailsHeaderSkeleton.tsx - Skeleton for details page header
    • PredictDetailsHeaderSkeleton.test.tsx - Unit tests
    • index.ts - Barrel export
  3. app/components/UI/Predict/components/PredictDetailsContentSkeleton/

    • PredictDetailsContentSkeleton.tsx - Skeleton for details page content
    • PredictDetailsContentSkeleton.test.tsx - Unit tests
    • index.ts - Barrel export
  4. app/components/UI/Predict/components/PredictDetailsButtonsSkeleton/

    • PredictDetailsButtonsSkeleton.tsx - Skeleton for action buttons
    • PredictDetailsButtonsSkeleton.test.tsx - Unit tests
    • index.ts - Barrel export
  5. app/components/UI/Predict/components/PredictPositionSkeleton/

    • PredictPositionSkeleton.tsx - Skeleton for position list items
    • PredictPositionSkeleton.test.tsx - Unit tests
    • index.ts - Barrel export

Modified Files

  1. app/components/UI/Predict/components/MarketListContent/MarketListContent.tsx

    • Integrated PredictMarketSkeleton for loading states
  2. app/components/UI/Predict/views/PredictMarketDetails/PredictMarketDetails.tsx

    • Integrated header, content, and button skeletons
    • Modified conditional rendering logic
  3. app/components/UI/Predict/views/PredictMarketDetails/PredictMarketDetails.test.tsx

    • Updated tests to expect new skeleton components
  4. app/components/UI/Predict/components/PredictPositionsHeader/PredictPositionsHeader.tsx

    • Added inline skeletons for balance and P&L values
    • Modified loading state logic
  5. app/components/UI/Predict/components/PredictPositionsHeader/PredictPositionsHeader.test.tsx

    • Updated tests for new loading behavior
  6. app/components/UI/Predict/components/PredictPositions/PredictPositions.tsx

    • Replaced ActivityIndicator with PredictPositionSkeleton components
  7. app/components/UI/Predict/components/PredictPositions/PredictPositions.test.tsx

    • Updated tests to expect skeleton components
  8. app/components/UI/Predict/views/PredictBuyPreview/PredictBuyPreview.tsx

    • Added smart skeleton for "To win" value with value-change detection
  9. app/components/UI/Predict/hooks/usePredictOrderPreview.ts

    • Exposed isCalculating state for skeleton logic

Technical Notes

Design System Compliance

  • All skeletons use Box component from @metamask/design-system-react-native
  • Styling uses useTailwind() hook and twClassName prop
  • No StyleSheet usage (adheres to workspace rules)
  • Uses design system color tokens (bg-muted, bg-section, etc.)

Testing Strategy

  • Unit tests for all new skeleton components
  • Integration tests updated for modified components
  • All tests use React Testing Library best practices

Performance Considerations

  • Skeleton components are lightweight (no heavy computations)
  • Smart debouncing prevents excessive skeleton re-renders
  • Independent loading states prevent unnecessary full-page re-renders

Review Focus Areas

  1. Visual Accuracy: Do skeletons match the Figma designs and real content structure?
  2. Loading Logic: Are skeletons appearing at the right times and transitioning smoothly?
  3. User Experience: Does this feel smoother than the previous loading indicators?
  4. Test Coverage: Do tests properly cover loading states and transitions?
  5. Code Organization: Are components modular and reusable?

Note

Adds dedicated skeleton loaders for market lists, market details, positions, and buy preview, replacing spinners and refining loading logic.

  • Predict UI:
    • Market List (MarketListContent.tsx): Replace generic loading/footer placeholders with PredictMarketSkeleton cards for initial load and pagination.
    • Market Details (PredictMarketDetails.tsx): Add PredictDetailsHeaderSkeleton, PredictDetailsContentSkeleton, and PredictDetailsButtonsSkeleton; render skeletons during initial fetch; keep chart loading independent; refine header rendering when route params missing.
    • Positions:
      • Header (PredictPositionsHeader.tsx): Inline skeletons for Available Balance and Unrealized P&L; always render card while loading; disable arrow while loading.
      • List (PredictPositions.tsx): Replace ActivityIndicator with 4 PredictPositionSkeleton rows during initial/refresh loads; gate PredictNewButton when loading.
    • Buy Preview (PredictBuyPreview.tsx): Show smart skeleton for "To win" value only while recalculating due to user input; keep balance skeleton.
  • New Components: PredictMarketSkeleton, PredictDetailsHeaderSkeleton, PredictDetailsContentSkeleton, PredictDetailsButtonsSkeleton, PredictPositionSkeleton (+ index files).
  • Misc: Simplify selectPredictEnabledFlag property check; update tests and snapshots to assert skeletons.

Written by Cursor Bugbot for commit 118502a. This will update automatically on new commits. Configure here.

- Create PredictMarketSkeleton component with updated design
- Update avatar from rounded square to circle (rounded-full)
- Adjust content area height from 158px to 180px
- Update spacing and border radius to match Figma specs
- Replace old skeleton implementation in MarketListContent
- Add unit tests for new skeleton component
- Reduce height from 180px to 150px for better proportions
- Update comment to reflect prediction options area instead of chart
- Remove chart skeleton from content skeleton (chart has its own loading)
- Create PredictDetailsButtonsSkeleton component for action buttons
- Update PredictDetailsContentSkeleton to show outcome cards with proper structure
  * Avatar (48x48), title/subtitle lines, and value columns
  * Map through 2 outcome cards for consistent skeleton layout
- Move skeleton button logic into renderActionButtons function
- Show skeleton buttons only when loading and market data not available
- Update all tests to reflect new skeleton structure
- Chart data comes from usePredictPriceHistory (independent from market data)
- Create skeleton loader for market details header
- Shows skeleton for back arrow, avatar, title, and subtitle
- Used when title is not available from route params
- Includes functional back button during loading state
- Add comprehensive unit tests
- Add skeleton for Available Balance value during loading
- Add skeleton for Unrealized P&L value during loading
- Show header card during loading instead of hiding completely
- Hide arrow icon during balance loading
- Disable TouchableOpacity when balance is loading
- Update visibility logic to show card when any section is loading or has data
- Remove early return check for isBalanceLoading condition
- Create PredictPositionSkeleton component matching real position card structure
  * Left: Avatar (40x40 rounded)
  * Middle: Title and subtitle lines
  * Right: Value and P&L lines
- Replace centered ActivityIndicator with 4 skeleton items during loading
- Show skeletons during initial load and refresh with no cached data
- Hide 'New' button during loading
- Remove unused ActivityIndicator, IconColor, useTailwind, and isHomepageRedesignV1Enabled imports
- Add comprehensive unit tests for skeleton component
- Update PredictDetailsContentSkeleton tests to match new value-1/value-2 testIDs
- Add NavigationContainer wrapper to PredictDetailsHeaderSkeleton tests
- Replace activity-indicator checks with skeleton loader checks in PredictPositions tests
- Update test assertions to expect 4 skeleton position items during loading
- Fix eslint errors in feature flag selector (remove unused imports)
- Add SafeAreaProvider to PredictDetailsHeaderSkeleton tests for useSafeAreaInsets
- Fix feature flag selector to properly read from state instead of hardcoding true
- Selector now returns flag value from remoteFlags or falls back to OVERRIDE_PREDICT_ENABLED_VALUE
- SafeAreaProvider requires initialMetrics prop to properly render children in tests
- Set default metrics with zero insets for test environment
- Replace NavigationContainer wrapper with jest.mock for useNavigation
- Mock useSafeAreaInsets to return zero insets for tests
- Simplify test rendering by using direct render() calls
- Follow standard pattern used across Predict component tests
✨ New Components Created:
- PredictMarketSkeleton: Market card skeleton for feed loading
- PredictPositionSkeleton: Position card skeleton for positions list
- PredictDetailsHeaderSkeleton: Market details header with functional back button
- PredictDetailsContentSkeleton: Market details content (outcome cards)
- PredictDetailsButtonsSkeleton: Market details action buttons

🔄 Components Updated:
- MarketListContent: Use PredictMarketSkeleton for loading states
- PredictPositions: Replace ActivityIndicator with 4 position skeletons
- PredictMarketDetails: Conditionally render header/content/button skeletons
- PredictPositionsHeader: Add inline skeletons for balance and P&L values

✅ Test Results:
- All 1840 tests passing
- Fixed all skeleton-related test failures
- Updated snapshots for new components
- Properly mocked navigation and safe area contexts

🎨 UX Improvements:
- Users now see content-aware loading states instead of spinners
- Loading skeletons match actual content structure
- Smoother perceived performance during data fetching
- Consistent loading experience across all views

📝 Implementation Details:
- Early return removed from components - show skeletons during loading
- Conditional rendering based on loading state and data availability
- Skeletons use proper design system tokens and Tailwind styling
- All components follow MetaMask design system guidelines
- Extract isCalculating from usePredictOrderPreview hook
- Track value changes using previousValueRef and isCalculating state
- Show skeleton only when value changes AND preview is calculating
- Clear skeleton and reset tracking when calculation completes or value is 0
- Auto-refresh updates happen silently without skeleton flashing
- Depend on both currentValue and isCalculating for proper tracking
- Fix eslint unused variable warning in feature flag selector
@metamaskbot metamaskbot added team-mobile-platform Mobile Platform team INVALID-PR-TEMPLATE PR's body doesn't match template labels Nov 11, 2025
- Split 'To win $120.00' assertion into separate 'To win' and '$120.00' checks
- Update test expectations to match component structure where label and value are separate Text components
- All 1840 tests now passing in Predict folder
@andrepimenta andrepimenta marked this pull request as ready for review November 11, 2025 14:40
Copy link
Contributor

@caieu caieu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sonarqubecloud
Copy link

@andrepimenta andrepimenta added this pull request to the merge queue Nov 11, 2025
Merged via the queue into main with commit 6e7d005 Nov 11, 2025
149 of 152 checks passed
@andrepimenta andrepimenta deleted the feature/predict/skeleton-loaders branch November 11, 2025 16:13
@github-actions github-actions bot locked and limited conversation to collaborators Nov 11, 2025
@metamaskbot metamaskbot added the release-7.60.0 Issue or pull request that will be included in release 7.60.0 label Nov 11, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

INVALID-PR-TEMPLATE PR's body doesn't match template release-7.60.0 Issue or pull request that will be included in release 7.60.0 size-XL team-mobile-platform Mobile Platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants