rn-button-component
Button Component
Guide for using the unified Button component with state management, icon support, and glass effect integration.
Overview
- Location:
@/components/Button - Features: State management, icon support, loading states, success/error states, glass effect on iOS
- Design: Rounded corners (borderRadius: 25), glass effect on iOS when available
Basic Usage
import { Button } from '@/components/Button';
<Button text="Click Me" onPress={() => console.log('Pressed')} />
Props
Required Props
text: string | React.ReactNode- Button label textonPress: () => void- Press handler function
Optional Props
variant?: 'primary' | 'secondary' | 'destructive'- Button style variant (default:'primary')buttonState?: ButtonState- Current button state (default:'default')loadingText?: string | React.ReactNode- Text shown during loading statesuccessText?: string | React.ReactNode- Text shown during success stateerrorText?: string | React.ReactNode- Text shown during error stateicon?: { name, size?, position? }- Icon configurationsuccessIcon?: { name, size? }- Icon shown in success state (default:'checkmark')reset?: boolean- Auto-reset from success to default after 2 seconds (default:false)setButtonState?: (state: ButtonState) => void- External state control functionstyle?: StyleProp<ViewStyle>- Additional styles
Button States
export type ButtonState = 'default' | 'disabled' | 'loading' | 'success' | 'error';
State Behavior
'default': Normal interactive state'disabled': Button is disabled and non-interactive'loading': Shows loading spinner with optionalloadingText'success': Shows success icon with optionalsuccessText'error': Shows error message witherrorText
Common Patterns
Basic Button
<Button text="Save" onPress={handleSave} />
Button with Variant
<Button text="Cancel" onPress={handleCancel} variant="secondary" />
<Button text="Delete" onPress={handleDelete} variant="destructive" />
Loading State
const [isSaving, setIsSaving] = useState(false);
<Button
text="Save"
onPress={handleSave}
buttonState={isSaving ? 'loading' : 'default'}
loadingText="Saving..."
/>
Disabled State
<Button
text="Submit"
onPress={handleSubmit}
buttonState={!isValid ? 'disabled' : 'default'}
/>
Button with Icon
<Button
text="Create Lesson"
onPress={handleCreate}
icon={{ name: 'add', size: 24, position: 'left' }}
variant="secondary"
/>
Success State with Auto-Reset
const [buttonState, setButtonState] = useState<ButtonState>('default');
const handleSave = async () => {
setButtonState('loading');
try {
await saveData();
setButtonState('success');
} catch (error) {
setButtonState('error');
}
};
<Button
text="Save"
onPress={handleSave}
buttonState={buttonState}
setButtonState={setButtonState}
loadingText="Saving..."
successText="Saved!"
errorText="Failed to save"
reset={true} // Auto-resets to 'default' after 2 seconds
/>
Combined Loading and Disabled
<Button
text="Save Lesson"
onPress={handleSave}
buttonState={isSaving ? 'loading' : isLoading ? 'disabled' : 'default'}
loadingText="Saving..."
/>
Icon Configuration
Icon Props
icon?: {
name: keyof typeof Ionicons.glyphMap; // Required: Ionicons icon name
size?: number; // Optional: Icon size (default: 24)
position?: 'left' | 'right'; // Optional: Icon position (default: 'left')
}
Examples
// Left icon (default)
<Button
text="Add Item"
icon={{ name: 'add', position: 'left' }}
onPress={handleAdd}
/>
// Right icon
<Button
text="Next"
icon={{ name: 'arrow-forward', position: 'right' }}
onPress={handleNext}
/>
// Custom size
<Button
text="Settings"
icon={{ name: 'settings', size: 20 }}
onPress={handleSettings}
/>
State Management
Internal State (Default)
The button manages its own state internally when setButtonState is not provided:
<Button
text="Click Me"
onPress={handleClick}
buttonState="loading" // State is managed internally
/>
External State Control
Provide setButtonState to control state externally:
const [state, setState] = useState<ButtonState>('default');
<Button
text="Submit"
onPress={handleSubmit}
buttonState={state}
setButtonState={setState}
loadingText="Submitting..."
/>
Auto-Reset Pattern
When reset={true} and button reaches 'success' state, it automatically resets to 'default' after 2 seconds:
const [state, setState] = useState<ButtonState>('default');
<Button
text="Save"
onPress={async () => {
setState('loading');
await save();
setState('success');
}}
buttonState={state}
setButtonState={setState}
reset={true}
successText="Saved!"
/>
Glass Effect
The button automatically uses glass effect on iOS when available:
- iOS 26+: Uses
GlassViewwith liquid glass effect - Other platforms: Falls back to regular
Viewwith background color - Automatic: No configuration needed, handled internally
The glass effect respects:
variantprop for tint colorbuttonStatefor disabled/loading tint- Interactive glass effect enabled automatically
Styling
Default Styles
borderRadius: 25- Fully rounded cornerspaddingVertical: 14paddingHorizontal: 24minHeight: 48
Custom Styles
<Button
text="Custom Button"
onPress={handlePress}
style={{ marginTop: 16, width: '100%' }}
/>
Complete Examples
Form Submit Button
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitState, setSubmitState] = useState<ButtonState>('default');
const handleSubmit = async () => {
setIsSubmitting(true);
setSubmitState('loading');
try {
await submitForm();
setSubmitState('success');
} catch (error) {
setSubmitState('error');
} finally {
setIsSubmitting(false);
}
};
<Button
text="Submit Form"
onPress={handleSubmit}
buttonState={submitState}
setButtonState={setSubmitState}
loadingText="Submitting..."
successText="Submitted!"
errorText="Submission failed"
reset={true}
disabled={!isFormValid}
/>
Action Button with Icon
<Button
text="Create Lesson"
onPress={openModal}
icon={{ name: 'add', size: 24, position: 'left' }}
variant="secondary"
/>
Conditional Loading
<Button
text="Translate"
onPress={handleTranslate}
buttonState={isTranslating ? 'loading' : 'default'}
loadingText="Translating..."
variant="secondary"
/>
Best Practices
- Always provide
loadingTextwhen using loading state for better UX - Use
setButtonStatefor complex state management scenarios - Use
reset={true}for temporary success states (e.g., form submissions) - Provide
errorTextwhen handling error states - Use icons sparingly - only when they add clarity
- Choose appropriate variants - use
destructiveonly for destructive actions
TypeScript
import { Button, ButtonState } from '@/components/Button';
const [state, setState] = useState<ButtonState>('default');
References
- Component location:
components/Button.tsx - Uses:
expo-glass-effect,@expo/vector-icons, React NativePressable
More from jchaselubitz/drill-app
expo-router
Patterns for Expo Router including Stack configuration, native tabs, and file-based routing best practices. Apply when working with navigation, routing, or screen configuration.
106watermelondb
WatermelonDB models, observation patterns, and React integration. Use when writing or debugging model code, observers (findAndObserve, query.observe), or screens that display live-updating DB data.
37zod-v4-patterns
Ensures Zod v4 patterns are used correctly throughout the codebase. Apply when creating or modifying validation schemas, form schemas, or any Zod validators. Enforces v4 syntax and prevents deprecated v3 patterns.
5gemini-api
Patterns for using Google Gemini API with structured output, JSON mode, and proper configuration. Apply when implementing AI features, text generation, or working with Gemini models.
4