Skip to content

Bug: DSRN ButtonBase textClassName type and loading issue #797

@georgewrmarshall

Description

@georgewrmarshall

Description

Two issues discovered in ButtonBase component during ButtonHero development:

  1. Performance Issue: textClassName prop forces function creation even for static values, causing unnecessary re-renders
  2. Loading Text Bug: When isLoading=true and loadingText is provided, the button displays both the loading text AND the children text instead of replacing the children

Steps to Reproduce

Issue 1 - textClassName Performance:

  1. Create a ButtonBase component with static text styling
  2. Try to use textClassName="text-primary-inverse"
  3. Get TypeScript error: Type 'string' is not assignable to type '(pressed: boolean) => string'
  4. Must use textClassName={() => 'text-primary-inverse'} creating new function on every render

Issue 2 - Loading Text Display:

  1. Create a ButtonBase with children="Click Me"
  2. Set isLoading={true} and loadingText="Loading..."
  3. Render the component
  4. Observe both texts are displayed: "Loading...Click Me"

Expected Behavior

Issue 1: textClassName should accept both string and function:

// Should work for static values
textClassName="text-primary-inverse"

// Should work for dynamic values
textClassName={(pressed) => pressed ? 'text-pressed' : 'text-default'}

Issue 2: Loading text should replace children, not append to them:

  • Expected: Button displays only "Loading..."
  • Actual: Button displays "Loading...Click Me"

Screenshots

Test Output showing the bug:

● ButtonHero › handles loading state correctly

  expect(element).toHaveTextContent()

  Expected element to have text content:
    loading...
  Received:
    loading...Test Button

Accessibility output during loading:

<View accessibilityHint="Button is currently loading, please wait"
accessibilityLabel="loading..." accessibilityRole="button" accessibilityState={
{ "busy": true, "disabled": true, } } accessible={true} testID="loading-button"
/>

Environment

  • OS: macOS 24.5.0
  • React Native: MetaMask Mobile app
  • Package: @metamask/design-system-react-native
  • Component: ButtonBase

Additional Context

Issue 1 Impact:

  • Forces unnecessary function creation on every render
  • Prevents memoization optimizations
  • Makes API less ergonomic for simple use cases
  • Common anti-pattern in React performance

Issue 2 Impact:

  • Poor UX - confusing display during loading states
  • Accessibility issues - screen readers get conflicting information
  • May cause layout issues with longer text combinations

Suggested Fixes:

  1. textClassName API: Change type to string | ((pressed: boolean) => string)
  2. Loading behavior: When isLoading=true, only show loading content, hide children

Workaround Currently Used:

// Issue 1 workaround
const getTextClassName = useCallback(() => 'text-primary-inverse', []);
textClassName = { getTextClassName };

// Issue 2 workaround
// Test for loading text presence rather than exact text content
expect(getByText('loading...')).toBeOnTheScreen();

Related Files:

Both issues were discovered during implementation of a new ButtonHero component in MetaMask Mobile that extends ButtonBase functionality from @metamask/design-system-react-native.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions