
React Native Elements
- 202 installs
- 191 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
react-native-elements: A skill for development. This provides functionality for development workflows.
Key points
- react-native-elements
React Native Elements by the numbers
- 202 all-time installs (skills.sh)
- +6 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,942 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pproenca/dot-skills --skill react-native-elementsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 202 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I use react-native-elements for development tasks?
Use react-native-elements for development tasks
Who is it for?
Best when you're working on backend & apis and need structured help with react-native-elements.
Skip if: Teams with no backend & apis needs, or anyone wanting a generic chat assistant without this specific workflow.
When should I use this skill?
When you need to use react-native-elements for development tasks, or when react-native-elements: a skill for development. this provides functionality for development workflows.
What you get
Structured output aligned to react-native-elements: react-native-elements.
Files
Community React Native Elements Best Practices
Comprehensive best practices guide for React Native Elements applications. Contains 45 rules across 8 categories, prioritized by impact to guide component usage, theming, and performance optimization.
When to Apply
Reference these guidelines when:
- Setting up React Native Elements in a new project
- Configuring ThemeProvider and createTheme
- Building lists with ListItem components
- Implementing form inputs with Input and SearchBar
- Optimizing FlatList performance with RNE components
- Reviewing code using React Native Elements
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Import & Setup | CRITICAL | setup- |
| 2 | Theme Architecture | CRITICAL | theme- |
| 3 | Component Selection | HIGH | comp- |
| 4 | List Performance | HIGH | list- |
| 5 | Props & Configuration | MEDIUM-HIGH | props- |
| 6 | Styling Patterns | MEDIUM | style- |
| 7 | Callbacks & Events | MEDIUM | event- |
| 8 | Advanced Patterns | LOW | adv- |
Quick Reference
1. Import & Setup (CRITICAL)
- `setup-themeprovider` - Wrap app with ThemeProvider for consistent theming
- `setup-imports` - Use @rneui/themed vs @rneui/base correctly
- `setup-createtheme` - Type-safe theme configuration with createTheme
- `setup-tree-shaking` - Enable proper tree-shaking for bundle size
- `setup-safe-area` - Configure SafeAreaProvider for notched devices
2. Theme Architecture (CRITICAL)
- `theme-usetheme-hook` - Access theme reactively with useTheme
- `theme-dark-mode` - Configure light/dark mode with createTheme
- `theme-component-defaults` - Set component defaults in theme
- `theme-updatetheme` - Runtime theme updates without remount
- `theme-color-scheme` - Sync with system color scheme
- `theme-custom-colors` - Extend theme with custom colors safely
3. Component Selection (HIGH)
- `comp-listitem-over-view` - Use ListItem for list rows
- `comp-input-over-textinput` - Use Input for form fields
- `comp-searchbar-platform` - Platform-specific SearchBar variants
- `comp-button-type` - Use Button type prop for variants
- `comp-icon-source` - Choose Icon type wisely for bundle size
- `comp-avatar-vs-image` - Use Avatar for profile images
4. List Performance (HIGH)
- `list-memo-items` - Memoize ListItem in FlatList
- `list-keyextractor` - Always provide keyExtractor
- `list-getitemlayout` - Use getItemLayout for fixed heights
- `list-renderitem-callback` - Extract renderItem with useCallback
- `list-windowsize` - Configure windowSize for memory balance
- `list-virtualized` - Use FlatList over ScrollView
- `list-removeClipped` - Configure removeClippedSubviews carefully
5. Props & Configuration (MEDIUM-HIGH)
- `props-loading-state` - Use loading prop for async operations
- `props-disabled-styling` - Configure disabledStyle for feedback
- `props-input-validation` - Use errorMessage for validation
- `props-icon-configuration` - Configure Icon props correctly
- `props-searchbar-loading` - Show loading state in SearchBar
- `props-button-color` - Use color prop for semantic colors
6. Styling Patterns (MEDIUM)
- `style-stylesheet` - Use StyleSheet.create over inline objects
- `style-containerStyle` - Use containerStyle for wrappers
- `style-usememo-dynamic` - Memoize dynamic styles
- `style-theme-colors` - Use theme colors over hardcoded values
- `style-component-props` - Prefer component-specific style props
7. Callbacks & Events (MEDIUM)
- `event-usecallback` - Wrap handlers in useCallback
- `event-debounce-search` - Debounce SearchBar onChangeText
- `event-listitem-onpress` - Pass item data correctly
- `event-avoid-anonymous` - Avoid anonymous functions in renders
- `event-input-handlers` - Configure Input handlers efficiently
8. Advanced Patterns (LOW)
- `adv-custom-component` - Wrap RNE components correctly
- `adv-platform-specific` - Handle platform-specific props
- `adv-makeStyles` - Use makeStyles for theme-aware styles
- `adv-overlay-modal` - Choose Overlay vs Modal correctly
- `adv-image-component` - Configure Avatar ImageComponent for caching
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |
{Rule Title}
{1-3 sentences explaining WHY this matters. Focus on performance implications or correctness.}
Incorrect ({what's wrong}):
{Bad code example - production-realistic, not strawman}
{// Comments explaining the cost}Correct ({what's right}):
{Good code example - minimal diff from incorrect}
{// Comments explaining the benefit}{Optional sections as needed:}
Alternative ({context}):
{Alternative approach when applicable}When NOT to use this pattern:
- {Exception 1}
- {Exception 2}
Benefits:
- {Benefit 1}
- {Benefit 2}
Reference: [{Reference Title}]({Reference URL})
{
"version": "1.0.6",
"organization": "Community",
"technology": "React Native Elements",
"date": "February 2026",
"abstract": "Comprehensive best practices guide for React Native Elements applications, designed for AI agents and LLMs. Contains 45 rules across 8 categories, prioritized by impact from critical (import setup, theme architecture) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.",
"references": [
"https://reactnativeelements.com/docs",
"https://github.com/react-native-elements/react-native-elements",
"https://reactnative.dev/docs/performance",
"https://reactnative.dev/docs/optimizing-flatlist-configuration",
"https://www.callstack.com/ebooks/the-ultimate-guide-to-react-native-optimization"
]
}
Sections
This file defines all sections, their ordering, impact levels, and descriptions. The section ID (in parentheses) is the filename prefix used to group rules.
---
1. Import & Setup (setup)
Impact: CRITICAL Description: Correct imports and ThemeProvider configuration prevent bundle bloat and ensure consistent theming across your entire application.
2. Theme Architecture (theme)
Impact: CRITICAL Description: Centralized theme configuration affects entire app's visual consistency, reduces prop drilling, and enables seamless dark/light mode switching.
3. Component Selection (comp)
Impact: HIGH Description: Choosing the right React Native Elements component for each use case maximizes built-in optimizations and accessibility.
4. List Performance (list)
Impact: HIGH Description: FlatList optimization is the #1 mobile performance factor. Poor list handling causes jank, memory issues, and dropped frames.
5. Props & Configuration (props)
Impact: MEDIUM-HIGH Description: Proper prop usage enables accessibility features, loading states, platform-specific behavior, and avoids common configuration pitfalls.
6. Styling Patterns (style)
Impact: MEDIUM Description: Memoized styles and StyleSheet usage prevents object recreation on each render, reducing unnecessary re-renders.
7. Callbacks & Events (event)
Impact: MEDIUM Description: Stable callbacks prevent cascading re-renders in list items and child components, improving UI responsiveness.
8. Advanced Patterns (adv)
Impact: LOW Description: Complex patterns for specific use cases including custom components, platform-specific code, and performance edge cases.
Wrap RNE Components Correctly
When creating custom wrapper components around React Native Elements, you must use forwardRef and properly spread props to maintain theme context inheritance and allow consumers to pass refs. Breaking the prop chain or theme context causes styling inconsistencies and limits component usability.
Incorrect (breaking theme context and ref forwarding):
import { Button, ButtonProps } from '@rneui/themed';
// Bad: No ref forwarding, props not spread correctly
const MyButton = (props: { label: string; onPress: () => void }) => {
return (
<Button
title={props.label}
onPress={props.onPress}
// Other ButtonProps cannot be passed through
/>
);
};
// Bad: Wrapping with extra View breaks theme context
const WrappedButton = (props: ButtonProps) => {
return (
<View>
<Button {...props} />
</View>
);
};Correct (using forwardRef with proper prop spreading):
import React, { forwardRef } from 'react';
import { Button, ButtonProps } from '@rneui/themed';
import type { Button as ButtonType } from '@rneui/base';
// Good: forwardRef preserves ref access, props spread maintains flexibility
interface MyButtonProps extends ButtonProps {
variant?: 'primary' | 'secondary';
}
const MyButton = forwardRef<ButtonType, MyButtonProps>(
({ variant = 'primary', ...props }, ref) => {
return (
<Button
ref={ref}
type={variant === 'secondary' ? 'outline' : 'solid'}
{...props}
/>
);
}
);
MyButton.displayName = 'MyButton';
// Usage: All ButtonProps work, refs work, theme inherited
<MyButton
ref={buttonRef}
title="Submit"
onPress={handlePress}
loading={isLoading}
/>Reference: React Native Elements Components
Configure Avatar ImageComponent for Caching
The Avatar component uses React Native's default Image component which lacks persistent caching. For avatars loaded from remote URLs, this means repeated network requests and slower perceived performance. Providing a cached ImageComponent like FastImage dramatically improves load times and reduces data usage.
Incorrect (default Image without caching):
import { Avatar } from '@rneui/themed';
// Bad: Default Image re-fetches on every mount
const UserAvatar = ({ user }) => (
<Avatar
rounded
size="large"
source={{ uri: user.avatarUrl }}
// No caching - network request every time
/>
);
// Bad: List of avatars causes many redundant requests
const UserList = ({ users }) => (
<FlatList
data={users}
renderItem={({ item }) => (
<Avatar
rounded
source={{ uri: item.avatarUrl }}
// Each scroll may re-fetch these images
/>
)}
/>
);Correct (FastImage as ImageComponent for cached loading):
import { Avatar } from '@rneui/themed';
import FastImage from 'react-native-fast-image';
// Good: FastImage provides aggressive caching
const UserAvatar = ({ user }) => (
<Avatar
rounded
size="large"
source={{ uri: user.avatarUrl }}
// Custom ImageComponent with caching
ImageComponent={FastImage}
// FastImage-specific props for cache control
imageProps={{
resizeMode: FastImage.resizeMode.cover,
priority: FastImage.priority.normal,
}}
/>
);
// Good: Preload avatars for instant display
const UserList = ({ users }) => {
useEffect(() => {
// Preload all avatar URLs
FastImage.preload(
users.map((u) => ({ uri: u.avatarUrl }))
);
}, [users]);
return (
<FlatList
data={users}
renderItem={({ item }) => (
<Avatar
rounded
source={{ uri: item.avatarUrl }}
ImageComponent={FastImage}
imageProps={{
resizeMode: FastImage.resizeMode.cover,
}}
/>
)}
/>
);
};Reference: React Native Elements Avatar
Use makeStyles for Theme-Aware Dynamic Styles
The makeStyles hook from @rneui/themed provides theme-reactive styles with full TypeScript support and automatic memoization. Manual useTheme + useMemo patterns are verbose, error-prone, and require explicit dependency management. makeStyles handles all of this automatically.
Incorrect (manual useTheme + useMemo for computed styles):
import { useMemo } from 'react';
import { StyleSheet } from 'react-native';
import { useTheme } from '@rneui/themed';
const ProfileCard = ({ size }: { size: 'small' | 'large' }) => {
const { theme } = useTheme();
// Bad: Verbose, manual memoization, easy to miss dependencies
const styles = useMemo(
() =>
StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
padding: size === 'large' ? 20 : 10,
},
text: {
color: theme.colors.primary,
fontSize: size === 'large' ? 18 : 14,
},
}),
[theme.colors.background, theme.colors.primary, size] // Easy to miss deps
);
return (
<View style={styles.container}>
<Text style={styles.text}>Hello</Text>
</View>
);
};Correct (makeStyles hook for type-safe theme-aware styles):
import { makeStyles } from '@rneui/themed';
import { View, Text } from 'react-native';
// Good: Styles defined outside component, typed props support
const useStyles = makeStyles((theme, props: { size: 'small' | 'large' }) => ({
container: {
backgroundColor: theme.colors.background,
padding: props.size === 'large' ? 20 : 10,
},
text: {
color: theme.colors.primary,
fontSize: props.size === 'large' ? 18 : 14,
},
}));
const ProfileCard = ({ size }: { size: 'small' | 'large' }) => {
// Good: Automatic memoization, theme reactivity, type inference
const styles = useStyles({ size });
return (
<View style={styles.container}>
<Text style={styles.text}>Hello</Text>
</View>
);
};Reference: React Native Elements Styles
Choose Overlay vs Modal Correctly
RNE's Overlay component is designed for dialogs and partial-screen content, while React Native's Modal is better for full-screen takeovers. Using Overlay for full-screen content causes z-index conflicts with navigation headers and improper accessibility announcements. Choose the right tool for each interaction pattern.
Incorrect (using Overlay for full-screen takeover):
import { Overlay } from '@rneui/themed';
// Bad: Overlay for full-screen content causes issues
const FullScreenMenu = ({ visible, onClose }) => (
<Overlay
isVisible={visible}
onBackdropPress={onClose}
// Full screen overlay fights with navigation z-index
fullScreen
overlayStyle={{
width: '100%',
height: '100%',
margin: 0,
padding: 0,
}}
>
<View style={{ flex: 1 }}>
<Text>Full screen content</Text>
{/* Header may appear above this overlay incorrectly */}
</View>
</Overlay>
);
// Bad: Modal for small dialog is overkill
const ConfirmDialog = ({ visible, onConfirm, onCancel }) => (
<Modal visible={visible} animationType="slide">
<View style={styles.dialogContainer}>
<Text>Are you sure?</Text>
<Button title="Yes" onPress={onConfirm} />
<Button title="No" onPress={onCancel} />
</View>
</Modal>
);Correct (Overlay for dialogs, Modal for full screens):
import { Modal } from 'react-native';
import { Overlay, Button } from '@rneui/themed';
// Good: Overlay for dialogs and partial-screen content
const ConfirmDialog = ({ visible, onConfirm, onCancel }) => (
<Overlay
isVisible={visible}
onBackdropPress={onCancel}
overlayStyle={{
width: '80%',
borderRadius: 12,
padding: 20,
}}
// Proper accessibility for dialogs
accessibilityViewIsModal
>
<Text style={{ marginBottom: 20 }}>Are you sure?</Text>
<Button title="Confirm" onPress={onConfirm} />
<Button title="Cancel" type="outline" onPress={onCancel} />
</Overlay>
);
// Good: Modal for full-screen takeovers
const FullScreenMenu = ({ visible, onClose }) => (
<Modal
visible={visible}
animationType="slide"
presentationStyle="fullScreen"
onRequestClose={onClose}
>
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<Button title="Close" onPress={onClose} />
<Text>Full screen content with proper z-index</Text>
</View>
</SafeAreaView>
</Modal>
);Reference: React Native Elements Overlay
Handle Platform-Specific Props
iOS and Android have different design languages and user expectations. Using Platform.select allows you to configure RNE components with platform-appropriate props, delivering native-feeling experiences on both platforms without maintaining separate component trees.
Incorrect (same props for both platforms):
import { Button, SearchBar } from '@rneui/themed';
// Bad: iOS-style elevation looks wrong on Android
const MyButton = () => (
<Button
title="Submit"
raised
// Same shadow/elevation for both platforms
containerStyle={{
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
elevation: 5,
}}
/>
);
// Bad: Generic SearchBar ignores platform conventions
const MySearch = () => (
<SearchBar
placeholder="Search..."
// No platform prop - uses default which may not match OS
/>
);Correct (platform-specific configuration):
import { Platform } from 'react-native';
import { Button, SearchBar } from '@rneui/themed';
// Good: Platform-appropriate styling
const MyButton = () => (
<Button
title="Submit"
raised
containerStyle={Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
android: {
elevation: 5,
},
})}
// Platform-specific button radius
buttonStyle={{
borderRadius: Platform.select({ ios: 8, android: 4 }),
}}
/>
);
// Good: SearchBar respects platform design language
const MySearch = () => (
<SearchBar
placeholder="Search..."
platform={Platform.OS === 'ios' ? 'ios' : 'android'}
// Platform-specific cancel button behavior
showCancel={Platform.OS === 'ios'}
cancelButtonTitle="Cancel"
/>
);Reference: React Native Elements SearchBar
Use Avatar for Profile and User Images
The Avatar component provides built-in rounded styling, placeholder handling, initials fallback for missing images, and accessory badges. Using the basic Image component for user avatars requires manually implementing all these features, leading to inconsistent profile displays and poor fallback handling.
Incorrect (Image with manual rounded styling and fallback):
// Bad: Manual implementation of avatar features
const UserAvatar = ({ user }) => {
const [imageError, setImageError] = useState(false);
const getInitials = (name) => {
return name
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase();
};
if (imageError || !user.avatarUrl) {
return (
<View style={styles.initialsContainer}>
<Text style={styles.initials}>{getInitials(user.name)}</Text>
</View>
);
}
return (
<Image
source={{ uri: user.avatarUrl }}
style={styles.avatar}
onError={() => setImageError(true)}
/>
);
};
// Lots of manual styling
const styles = StyleSheet.create({
avatar: { width: 50, height: 50, borderRadius: 25 },
initialsContainer: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: '#ccc',
justifyContent: 'center',
alignItems: 'center',
},
initials: { color: '#fff', fontSize: 18, fontWeight: '600' },
});Correct (Avatar with built-in features):
import { Avatar } from '@rneui/themed';
// Good: Built-in rounded, placeholder, and error handling
const UserAvatar = ({ user }) => (
<Avatar
rounded
size="medium"
source={{ uri: user.avatarUrl }}
title={user.name
.split(' ')
.map((n) => n[0])
.join('')}
/>
);
// Size variants
const AvatarSizes = ({ user }) => (
<View style={{ flexDirection: 'row', gap: 8 }}>
<Avatar rounded size="small" source={{ uri: user.avatarUrl }} />
<Avatar rounded size="medium" source={{ uri: user.avatarUrl }} />
<Avatar rounded size="large" source={{ uri: user.avatarUrl }} />
<Avatar rounded size="xlarge" source={{ uri: user.avatarUrl }} />
</View>
);
// With accessory badge (online status, edit button)
const AvatarWithStatus = ({ user, isOnline }) => (
<Avatar
rounded
size="large"
source={{ uri: user.avatarUrl }}
title={user.initials}
>
<Avatar.Accessory
size={18}
style={{ backgroundColor: isOnline ? 'green' : 'gray' }}
/>
</Avatar>
);
// Editable avatar with camera icon
const EditableAvatar = ({ user, onEdit }) => (
<Avatar
rounded
size="xlarge"
source={{ uri: user.avatarUrl }}
title={user.initials}
onPress={onEdit}
>
<Avatar.Accessory size={24} name="camera" type="material" onPress={onEdit} />
</Avatar>
);
// Icon avatar for placeholder or system users
const SystemAvatar = () => (
<Avatar
rounded
size="medium"
icon={{ name: 'person', type: 'material' }}
containerStyle={{ backgroundColor: '#c2c2c2' }}
/>
);
// Avatar group for multiple users
const AvatarGroup = ({ users }) => (
<View style={{ flexDirection: 'row' }}>
{users.slice(0, 3).map((user, index) => (
<Avatar
key={user.id}
rounded
size="small"
source={{ uri: user.avatarUrl }}
title={user.initials}
containerStyle={{ marginLeft: index > 0 ? -10 : 0 }}
/>
))}
{users.length > 3 && (
<Avatar
rounded
size="small"
title={`+${users.length - 3}`}
containerStyle={{ marginLeft: -10, backgroundColor: '#666' }}
/>
)}
</View>
);Reference: React Native Elements Avatar
Use Button Type Prop for Variants
The Button component's type prop provides solid, clear, and outline variants with proper accessibility, touch feedback, and theme integration. Creating custom button variants with TouchableOpacity requires manually handling all these concerns, leading to inconsistent button behavior across your app.
Incorrect (custom styled TouchableOpacity for variants):
// Bad: Manual button implementations
const PrimaryButton = ({ title, onPress }) => (
<TouchableOpacity
onPress={onPress}
style={styles.primaryButton}
activeOpacity={0.7}
>
<Text style={styles.primaryText}>{title}</Text>
</TouchableOpacity>
);
const OutlineButton = ({ title, onPress }) => (
<TouchableOpacity
onPress={onPress}
style={styles.outlineButton}
activeOpacity={0.7}
>
<Text style={styles.outlineText}>{title}</Text>
</TouchableOpacity>
);
const TextButton = ({ title, onPress }) => (
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
<Text style={styles.textButton}>{title}</Text>
</TouchableOpacity>
);
// Many lines of manual styling required
const styles = StyleSheet.create({
primaryButton: { backgroundColor: '#2089dc', padding: 16, borderRadius: 8 },
primaryText: { color: '#fff', textAlign: 'center', fontWeight: '600' },
outlineButton: { borderWidth: 2, borderColor: '#2089dc', padding: 16, borderRadius: 8 },
outlineText: { color: '#2089dc', textAlign: 'center', fontWeight: '600' },
textButton: { color: '#2089dc', textAlign: 'center' },
});Correct (Button with type prop):
import { Button } from '@rneui/themed';
// Good: Built-in variants with proper accessibility
const ButtonVariants = () => (
<View>
{/* Solid (default) - filled background */}
<Button title="Primary Action" type="solid" />
{/* Outline - bordered with transparent background */}
<Button title="Secondary Action" type="outline" />
{/* Clear - text only, no background or border */}
<Button title="Tertiary Action" type="clear" />
</View>
);
// With icons and loading states
const ActionButtons = ({ isLoading }) => (
<View>
<Button
title="Save"
type="solid"
icon={{ name: 'save', type: 'material', color: 'white' }}
loading={isLoading}
disabled={isLoading}
/>
<Button
title="Edit"
type="outline"
icon={{ name: 'edit', type: 'material', color: '#2089dc' }}
/>
<Button
title="Delete"
type="clear"
icon={{ name: 'delete', type: 'material', color: 'red' }}
titleStyle={{ color: 'red' }}
/>
</View>
);
// Size variants
const SizedButtons = () => (
<View>
<Button title="Small" size="sm" />
<Button title="Medium" size="md" />
<Button title="Large" size="lg" />
</View>
);
// Custom colors while keeping variants
const ThemedButtons = () => (
<View>
<Button title="Success" type="solid" color="success" />
<Button title="Warning" type="outline" color="warning" />
<Button title="Error" type="clear" color="error" />
</View>
);Reference: React Native Elements Button
Choose Icon Type Prop Wisely
The Icon component supports multiple icon libraries through the type prop, but each library has drastically different bundle sizes. MaterialIcons (~100KB) is included with Expo by default, while FontAwesome5 can add 2MB+. Choose the smallest library that covers your icon needs to avoid bundle bloat.
Incorrect (using heavy icon library for few icons):
// Bad: FontAwesome5 Pro adds ~2MB for just a few icons
import { Icon } from '@rneui/themed';
const Navigation = () => (
<View>
{/* Using font-awesome-5 for basic icons available in smaller libraries */}
<Icon name="home" type="font-awesome-5" />
<Icon name="user" type="font-awesome-5" />
<Icon name="cog" type="font-awesome-5" />
<Icon name="search" type="font-awesome-5" />
</View>
);
// Bad: Mixing multiple heavy icon libraries
const MixedIcons = () => (
<View>
<Icon name="home" type="font-awesome-5" />
<Icon name="settings" type="ionicon" />
<Icon name="person" type="material" />
{/* Three different libraries in bundle */}
</View>
);Correct (selecting appropriate icon library):
import { Icon } from '@rneui/themed';
// Good: Use Material Icons (included with Expo, ~100KB)
const Navigation = () => (
<View>
<Icon name="home" type="material" />
<Icon name="person" type="material" />
<Icon name="settings" type="material" />
<Icon name="search" type="material" />
</View>
);
// Good: Stick to one library throughout the app
const ConsistentIcons = () => (
<View>
<Icon name="home" type="material" />
<Icon name="favorite" type="material" />
<Icon name="star" type="material" />
<Icon name="share" type="material" />
</View>
);
// Good: Use specific imports for custom icons
import { MaterialIcons } from '@expo/vector-icons';
const CustomIconButton = () => (
<Button
icon={<MaterialIcons name="add" size={24} color="white" />}
title="Add Item"
/>
);
// Good: Create an icon mapping for app-wide consistency
const AppIcons = {
home: { name: 'home', type: 'material' },
profile: { name: 'person', type: 'material' },
settings: { name: 'settings', type: 'material' },
search: { name: 'search', type: 'material' },
notifications: { name: 'notifications', type: 'material' },
} as const;
const IconButton = ({ icon }: { icon: keyof typeof AppIcons }) => (
<Icon {...AppIcons[icon]} />
);
// Icon library size reference:
// - material: ~100KB (default, recommended)
// - material-community: ~400KB (extended material)
// - ionicon: ~200KB (iOS-style)
// - font-awesome: ~200KB (v4)
// - font-awesome-5: ~2MB+ (v5, heaviest)
// - feather: ~50KB (minimal, outlined)
// - antdesign: ~300KB (Ant Design)Reference: React Native Elements Icon
Use Input Component for Form Fields
The Input component bundles label, error message display, icons, and accessibility features that would otherwise require 50+ lines of custom code. Using raw TextInput forces you to manually handle these common form patterns, leading to inconsistent implementations across your app.
Incorrect (raw TextInput with manual handling):
// Bad: Manual implementation of common form patterns
const EmailField = ({ value, onChange, error }) => (
<View style={styles.inputContainer}>
<Text style={styles.label}>Email Address</Text>
<View style={styles.inputWrapper}>
<Icon name="email" style={styles.icon} />
<TextInput
value={value}
onChangeText={onChange}
placeholder="Enter your email"
style={[styles.input, error && styles.inputError]}
keyboardType="email-address"
autoCapitalize="none"
/>
</View>
{error && <Text style={styles.errorText}>{error}</Text>}
</View>
);
// Requires extensive manual styling
const styles = StyleSheet.create({
inputContainer: { marginBottom: 16 },
label: { fontSize: 14, fontWeight: '600', marginBottom: 4 },
inputWrapper: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderRadius: 8 },
icon: { padding: 10 },
input: { flex: 1, padding: 12 },
inputError: { borderColor: 'red' },
errorText: { color: 'red', fontSize: 12, marginTop: 4 },
});Correct (Input with built-in features):
import { Input, Icon } from '@rneui/themed';
// Good: All common form features built-in
const EmailField = ({ value, onChange, error }) => (
<Input
value={value}
onChangeText={onChange}
label="Email Address"
placeholder="Enter your email"
keyboardType="email-address"
autoCapitalize="none"
errorMessage={error}
leftIcon={<Icon name="email" type="material" />}
/>
);
// Password field with visibility toggle
const PasswordField = ({ value, onChange, error }) => {
const [visible, setVisible] = useState(false);
return (
<Input
value={value}
onChangeText={onChange}
label="Password"
placeholder="Enter your password"
secureTextEntry={!visible}
errorMessage={error}
leftIcon={<Icon name="lock" type="material" />}
rightIcon={
<Icon
name={visible ? 'visibility' : 'visibility-off'}
type="material"
onPress={() => setVisible(!visible)}
/>
}
/>
);
};
// Disabled and loading states
const DisabledInput = () => (
<Input
label="Username"
disabled
disabledInputStyle={{ opacity: 0.5 }}
placeholder="Loading..."
/>
);Reference: React Native Elements Input
Use ListItem for List Rows Instead of Custom Views
ListItem provides built-in accessibility labels, proper touch feedback, and consistent styling out of the box. Building custom list rows with View and TouchableOpacity requires manually implementing these features, leading to inconsistent UX and accessibility gaps.
Incorrect (custom View with TouchableOpacity):
// Bad: Manual implementation lacks built-in optimizations
const CustomListRow = ({ user, onPress }) => (
<TouchableOpacity onPress={onPress} style={styles.row}>
<View style={styles.avatarContainer}>
<Image source={{ uri: user.avatar }} style={styles.avatar} />
</View>
<View style={styles.textContainer}>
<Text style={styles.title}>{user.name}</Text>
<Text style={styles.subtitle}>{user.email}</Text>
</View>
<Icon name="chevron-right" />
</TouchableOpacity>
);
// Requires ~30 lines of manual styling
const styles = StyleSheet.create({
row: { flexDirection: 'row', padding: 16, alignItems: 'center' },
avatarContainer: { marginRight: 12 },
avatar: { width: 40, height: 40, borderRadius: 20 },
textContainer: { flex: 1 },
title: { fontSize: 16, fontWeight: '600' },
subtitle: { fontSize: 14, color: '#666' },
});Correct (ListItem with subcomponents):
import { ListItem, Avatar, Icon } from '@rneui/themed';
// Good: Built-in accessibility, touch handling, and theming
const UserListRow = ({ user, onPress }) => (
<ListItem onPress={onPress} bottomDivider>
<Avatar rounded source={{ uri: user.avatar }} />
<ListItem.Content>
<ListItem.Title>{user.name}</ListItem.Title>
<ListItem.Subtitle>{user.email}</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
);
// Swipeable variant with built-in actions
const SwipeableUserRow = ({ user, onDelete }) => (
<ListItem.Swipeable
rightContent={(reset) => (
<Button
title="Delete"
onPress={() => { onDelete(user.id); reset(); }}
icon={{ name: 'delete', color: 'white' }}
buttonStyle={{ minHeight: '100%', backgroundColor: 'red' }}
/>
)}
>
<Avatar rounded source={{ uri: user.avatar }} />
<ListItem.Content>
<ListItem.Title>{user.name}</ListItem.Title>
</ListItem.Content>
</ListItem.Swipeable>
);Reference: React Native Elements ListItem
Use Platform-Specific SearchBar Variants
SearchBar supports platform-specific styling that matches iOS and Android design guidelines. Using the generic default ignores platform conventions, making your app feel foreign to users. The platform prop automatically applies native styling, animations, and behaviors.
Incorrect (generic SearchBar ignoring platform):
// Bad: Generic styling doesn't match platform conventions
const Search = () => {
const [search, setSearch] = useState('');
return (
<SearchBar
placeholder="Search..."
value={search}
onChangeText={setSearch}
// No platform prop - uses default styling
/>
);
};
// Bad: Manually styling to look like platform
const ManualPlatformSearch = () => {
const [search, setSearch] = useState('');
return (
<SearchBar
placeholder="Search..."
value={search}
onChangeText={setSearch}
containerStyle={{
backgroundColor: Platform.OS === 'ios' ? '#f0f0f0' : '#fff',
borderTopWidth: Platform.OS === 'ios' ? 0 : 1,
// Lots of manual platform checks...
}}
/>
);
};Correct (platform-specific variants):
import { SearchBar } from '@rneui/themed';
import { Platform } from 'react-native';
// Good: Automatic platform-native styling
const PlatformSearch = () => {
const [search, setSearch] = useState('');
return (
<SearchBar
platform={Platform.OS === 'ios' ? 'ios' : 'android'}
placeholder="Search..."
value={search}
onChangeText={setSearch}
// iOS: Rounded corners, gray background, cancel button animation
// Android: Material design, elevation, underline focus
/>
);
};
// iOS-specific features
const IOSSearch = () => {
const [search, setSearch] = useState('');
return (
<SearchBar
platform="ios"
placeholder="Search..."
value={search}
onChangeText={setSearch}
cancelButtonTitle="Cancel"
showCancel={search.length > 0}
onCancel={() => setSearch('')}
/>
);
};
// Android-specific features
const AndroidSearch = () => {
const [search, setSearch] = useState('');
return (
<SearchBar
platform="android"
placeholder="Search..."
value={search}
onChangeText={setSearch}
searchIcon={{ size: 24 }}
clearIcon={{ size: 24 }}
// Material Design styling applied automatically
/>
);
};
// With loading state
const SearchWithLoading = ({ isSearching }) => {
const [search, setSearch] = useState('');
return (
<SearchBar
platform={Platform.OS === 'ios' ? 'ios' : 'android'}
placeholder="Search..."
value={search}
onChangeText={setSearch}
showLoading={isSearching}
/>
);
};Reference: React Native Elements SearchBar
Avoid Anonymous Functions in Renders
Anonymous functions defined inline during render create new function references every time the component renders. This defeats React.memo optimizations because props appear to have changed even when the function logic is identical. Pre-define handlers or use useCallback to maintain stable references.
Incorrect (anonymous function in JSX):
import { Button, Input } from '@rneui/themed';
function UserForm({ onSubmit, onCancel }) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
return (
<View>
{/* BAD: New function on every render */}
<Input
placeholder="Name"
onChangeText={(text) => setName(text)}
/>
{/* BAD: Arrow function creates new reference */}
<Input
placeholder="Email"
onChangeText={(text) => setEmail(text.toLowerCase())}
/>
{/* BAD: Anonymous function breaks Button memo */}
<Button
title="Submit"
onPress={() => onSubmit({ name, email })}
/>
{/* BAD: Even simple handlers create new refs */}
<Button
title="Cancel"
onPress={() => onCancel()}
/>
</View>
);
}Correct (pre-defined callbacks with useCallback):
import { useCallback, useState } from 'react';
import { Button, Input } from '@rneui/themed';
function UserForm({ onSubmit, onCancel }) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
// Stable reference - setName is stable from useState
const handleNameChange = useCallback((text: string) => {
setName(text);
}, []);
// Stable reference with transformation
const handleEmailChange = useCallback((text: string) => {
setEmail(text.toLowerCase());
}, []);
// Only recreated when dependencies change
const handleSubmit = useCallback(() => {
onSubmit({ name, email });
}, [onSubmit, name, email]);
// Simple wrapper with stable reference
const handleCancel = useCallback(() => {
onCancel();
}, [onCancel]);
return (
<View>
<Input
placeholder="Name"
onChangeText={handleNameChange}
/>
<Input
placeholder="Email"
onChangeText={handleEmailChange}
/>
<Button title="Submit" onPress={handleSubmit} />
<Button title="Cancel" onPress={handleCancel} />
</View>
);
}Reference: React Native Performance
Debounce SearchBar onChangeText
Firing API calls or expensive operations on every keystroke overwhelms both the network and the JavaScript thread. Users typing "react native" would trigger 12 separate requests without debouncing. A 300-500ms debounce delay ensures smooth typing while still feeling responsive.
Incorrect (API call on every keystroke):
import { SearchBar } from '@rneui/themed';
function ProductSearch() {
const [search, setSearch] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (text: string) => {
setSearch(text);
// BAD: API call fires on every keystroke
// Typing "shoes" = 5 API calls in rapid succession
fetch(`/api/search?q=${text}`)
.then((res) => res.json())
.then(setResults);
};
return (
<SearchBar
placeholder="Search products..."
onChangeText={handleSearch}
value={search}
/>
);
}Correct (debounced search with 300ms delay):
import { useState, useCallback, useMemo } from 'react';
import { SearchBar } from '@rneui/themed';
import debounce from 'lodash.debounce';
function ProductSearch() {
const [search, setSearch] = useState('');
const [results, setResults] = useState([]);
// Memoize the debounced API call
const debouncedSearch = useMemo(
() =>
debounce((text: string) => {
if (text.length > 2) {
fetch(`/api/search?q=${text}`)
.then((res) => res.json())
.then(setResults);
}
}, 300),
[]
);
// Update local state immediately, debounce API call
const handleSearch = useCallback(
(text: string) => {
setSearch(text); // Immediate UI update
debouncedSearch(text); // Debounced API call
},
[debouncedSearch]
);
// Cleanup on unmount
useEffect(() => {
return () => {
debouncedSearch.cancel();
};
}, [debouncedSearch]);
return (
<SearchBar
placeholder="Search products..."
onChangeText={handleSearch}
value={search}
/>
);
}Reference: React Native Performance
Configure Input Handlers Efficiently
Updating parent component state on every keystroke triggers re-renders up the entire component tree. This causes input lag and janky typing experience, especially in complex forms. Buffer input locally and sync to parent state only when needed using debounce or blur events.
Incorrect (direct parent state update on every keystroke):
import { Input } from '@rneui/themed';
// Parent component re-renders on every keystroke
function ProfileForm({ formData, setFormData }) {
return (
<View>
{/* BAD: Every keystroke updates parent state */}
<Input
label="Username"
value={formData.username}
onChangeText={(text) =>
// Triggers parent re-render, which re-renders entire form
setFormData({ ...formData, username: text })
}
/>
<Input
label="Bio"
value={formData.bio}
multiline
onChangeText={(text) =>
// Long text input becomes very laggy
setFormData({ ...formData, bio: text })
}
/>
{/* Other expensive components re-render on each keystroke */}
<ExpensiveAvatarPicker user={formData} />
</View>
);
}Correct (local state with debounced/blur sync to parent):
import { useState, useCallback, useEffect, useMemo } from 'react';
import { Input } from '@rneui/themed';
import debounce from 'lodash.debounce';
// Isolated input with local state
function BufferedInput({ label, value, onValueChange, multiline }) {
const [localValue, setLocalValue] = useState(value);
// Sync from parent when value prop changes externally
useEffect(() => {
setLocalValue(value);
}, [value]);
// Debounced update to parent - only fires after typing stops
const debouncedUpdate = useMemo(
() => debounce((text: string) => onValueChange(text), 300),
[onValueChange]
);
const handleChange = useCallback(
(text: string) => {
setLocalValue(text); // Immediate local update
debouncedUpdate(text); // Debounced parent update
},
[debouncedUpdate]
);
// Cleanup debounce on unmount
useEffect(() => {
return () => debouncedUpdate.cancel();
}, [debouncedUpdate]);
return (
<Input
label={label}
value={localValue}
onChangeText={handleChange}
multiline={multiline}
/>
);
}
// Parent only re-renders when debounce fires
function ProfileForm({ formData, setFormData }) {
const updateField = useCallback(
(field: string) => (value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
},
[setFormData]
);
return (
<View>
<BufferedInput
label="Username"
value={formData.username}
onValueChange={updateField('username')}
/>
<BufferedInput
label="Bio"
value={formData.bio}
onValueChange={updateField('bio')}
multiline
/>
{/* Only re-renders when debounce fires, not every keystroke */}
<ExpensiveAvatarPicker user={formData} />
</View>
);
}Reference: React Native Performance
Pass Item Data Correctly in ListItem onPress
JavaScript closures can capture stale references when iterating over lists, especially with asynchronous updates. Passing item data correctly ensures your press handlers always operate on the current, correct data rather than outdated values from a previous render cycle.
Incorrect (closure captures stale item reference):
import { ListItem } from '@rneui/themed';
function TaskList({ tasks, onTaskComplete }) {
// BAD: item reference may be stale if list updates during render
return (
<>
{tasks.map((item) => (
<ListItem
key={item.id}
onPress={() => {
// This closure captures 'item' at render time
// If tasks array updates, this still references old item
onTaskComplete(item);
}}
>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
</ListItem.Content>
</ListItem>
))}
</>
);
}Correct (use stable ID and lookup current data):
import { useCallback } from 'react';
import { ListItem } from '@rneui/themed';
function TaskList({ tasks, onTaskComplete }) {
// Store tasks in ref for fresh access, or use ID-based lookup
const handlePress = useCallback(
(taskId: string) => {
// Lookup current task data at press time, not render time
const currentTask = tasks.find((t) => t.id === taskId);
if (currentTask) {
onTaskComplete(currentTask);
}
},
[tasks, onTaskComplete]
);
return (
<>
{tasks.map((item) => (
<MemoizedTaskItem
key={item.id}
item={item}
onPress={handlePress}
/>
))}
</>
);
}
// Extract to memoized component for better performance
const MemoizedTaskItem = React.memo(({ item, onPress }) => {
// Pass only the stable ID, not the whole object
const handleItemPress = useCallback(() => {
onPress(item.id);
}, [item.id, onPress]);
return (
<ListItem onPress={handleItemPress}>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
</ListItem.Content>
</ListItem>
);
});Reference: React Native Performance
Wrap Event Handlers in useCallback
Creating inline functions in render creates new function references on every render cycle. This breaks React.memo optimizations on child components and causes unnecessary re-renders throughout your component tree.
Incorrect (inline arrow function creates new reference each render):
import { ListItem } from '@rneui/themed';
function ItemList({ items, onItemSelect }) {
return (
<>
{items.map((item) => (
<ListItem
key={item.id}
// New function created on every render - breaks memo
onPress={() => onItemSelect(item.id)}
>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
</ListItem.Content>
</ListItem>
))}
</>
);
}Correct (useCallback provides stable reference):
import { useCallback } from 'react';
import { ListItem } from '@rneui/themed';
function ItemList({ items, onItemSelect }) {
// Stable function reference - only changes when onItemSelect changes
const handlePress = useCallback(
(itemId: string) => {
onItemSelect(itemId);
},
[onItemSelect]
);
return (
<>
{items.map((item) => (
<ListItem
key={item.id}
// Pass stable reference with item data
onPress={() => handlePress(item.id)}
>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
</ListItem.Content>
</ListItem>
))}
</>
);
}
// For maximum optimization, extract to memoized component
const MemoizedListItem = React.memo(({ item, onPress }) => (
<ListItem onPress={onPress}>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
</ListItem.Content>
</ListItem>
));Reference: React Native Performance
Use getItemLayout for Fixed-Height ListItems
When ListItems have a fixed height, providing getItemLayout allows FlatList to calculate item positions synchronously without measuring. This enables instant scrollToIndex(), eliminates blank spaces during fast scrolling, and significantly improves initial render performance for long lists.
Incorrect (missing getItemLayout with fixed-height items):
import { ListItem } from '@rneui/themed';
import { FlatList } from 'react-native';
import { useRef } from 'react';
// Bad: Without getItemLayout, scrollToIndex requires async measurement
const AlphabetList = ({ contacts }) => {
const listRef = useRef(null);
const scrollToLetter = (letterIndex) => {
// This will be slow or fail without getItemLayout
// FlatList must measure items to find position
listRef.current?.scrollToIndex({ index: letterIndex });
};
return (
<FlatList
ref={listRef}
data={contacts}
renderItem={({ item }) => (
// ListItem has consistent 72px height but FlatList doesn't know
<ListItem containerStyle={{ height: 72 }}>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>{item.phone}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
)}
keyExtractor={(item) => item.id}
// Missing getItemLayout - scrollToIndex will be unreliable
/>
);
};Correct (getItemLayout for uniform ListItem heights):
import { ListItem } from '@rneui/themed';
import { FlatList } from 'react-native';
import { useRef, useCallback } from 'react';
const ITEM_HEIGHT = 72;
const SEPARATOR_HEIGHT = 1;
const AlphabetList = ({ contacts }) => {
const listRef = useRef(null);
// Good: Pre-calculate item positions for instant access
const getItemLayout = useCallback((data, index) => ({
length: ITEM_HEIGHT,
offset: (ITEM_HEIGHT + SEPARATOR_HEIGHT) * index,
index,
}), []);
const scrollToLetter = (letterIndex) => {
// Now instant - no measurement needed
listRef.current?.scrollToIndex({
index: letterIndex,
animated: true,
});
};
return (
<FlatList
ref={listRef}
data={contacts}
renderItem={({ item }) => (
<ListItem containerStyle={{ height: ITEM_HEIGHT }}>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>{item.phone}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
)}
keyExtractor={(item) => item.id}
// Good: Enables synchronous position calculation
getItemLayout={getItemLayout}
ItemSeparatorComponent={() => (
<View style={{ height: SEPARATOR_HEIGHT, backgroundColor: '#eee' }} />
)}
/>
);
};Reference: React Native FlatList Optimization
Always Provide keyExtractor for FlatList
FlatList uses keys to track which items have changed, been added, or removed. Without a proper keyExtractor using unique identifiers, React cannot efficiently diff the list, leading to incorrect renders, duplicate key warnings, and wasted re-renders when items are reordered.
Incorrect (missing keyExtractor or using index as key):
import { ListItem } from '@rneui/themed';
import { FlatList } from 'react-native';
// Bad: No keyExtractor - React uses index by default
const TaskList = ({ tasks }) => {
return (
<FlatList
data={tasks}
renderItem={({ item }) => (
<ListItem>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
</ListItem.Content>
</ListItem>
)}
// Missing keyExtractor!
/>
);
};
// Also bad: Using index as key breaks diffing on reorder/delete
const TaskListWithIndex = ({ tasks }) => {
return (
<FlatList
data={tasks}
renderItem={({ item }) => (
<ListItem>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
</ListItem.Content>
</ListItem>
)}
// Bad: Index changes when items are reordered or deleted
keyExtractor={(item, index) => index.toString()}
/>
);
};Correct (keyExtractor using unique stable identifier):
import { ListItem } from '@rneui/themed';
import { FlatList } from 'react-native';
// Good: Use a unique, stable identifier from your data
const TaskList = ({ tasks }) => {
return (
<FlatList
data={tasks}
renderItem={({ item }) => (
<ListItem>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
</ListItem.Content>
</ListItem>
)}
// Good: Unique ID that doesn't change when list is reordered
keyExtractor={(item) => item.id}
/>
);
};
// For items without ID, use a combination of unique fields
const ProductList = ({ products }) => {
return (
<FlatList
data={products}
renderItem={({ item }) => (
<ListItem>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>${item.price}</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
)}
// Good: Composite key from unique combination
keyExtractor={(item) => `${item.sku}-${item.warehouseId}`}
/>
);
};Reference: React Native FlatList Optimization
Memoize ListItem Components in FlatList
When FlatList data changes or parent re-renders, every visible ListItem re-renders by default, even if their props haven't changed. Wrapping ListItem components with React.memo() ensures only items with changed props re-render, dramatically improving scroll smoothness and reducing CPU usage.
Incorrect (plain ListItem re-renders on every list update):
import { ListItem } from '@rneui/themed';
import { FlatList } from 'react-native';
// Bad: This component re-renders whenever the parent list updates,
// even if this specific item's data hasn't changed
const ContactItem = ({ item, onPress }) => {
return (
<ListItem onPress={() => onPress(item.id)}>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>{item.email}</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
);
};
const ContactList = ({ contacts, onContactPress }) => {
return (
<FlatList
data={contacts}
renderItem={({ item }) => (
<ContactItem item={item} onPress={onContactPress} />
)}
keyExtractor={(item) => item.id}
/>
);
};Correct (React.memo wrapped ListItem with proper comparison):
import { ListItem } from '@rneui/themed';
import { FlatList } from 'react-native';
import React, { memo, useCallback } from 'react';
// Good: Memoized component only re-renders when item or onPress changes
const ContactItem = memo(({ item, onPress }) => {
return (
<ListItem onPress={() => onPress(item.id)}>
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>{item.email}</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
);
}, (prevProps, nextProps) => {
// Custom comparison for optimal performance
return prevProps.item.id === nextProps.item.id &&
prevProps.item.name === nextProps.item.name &&
prevProps.item.email === nextProps.item.email &&
prevProps.onPress === nextProps.onPress;
});
const ContactList = ({ contacts, onContactPress }) => {
// Stable callback reference prevents unnecessary re-renders
const handlePress = useCallback((id) => {
onContactPress(id);
}, [onContactPress]);
return (
<FlatList
data={contacts}
renderItem={({ item }) => (
<ContactItem item={item} onPress={handlePress} />
)}
keyExtractor={(item) => item.id}
/>
);
};Reference: React Native FlatList Optimization
Configure removeClippedSubviews Carefully
The removeClippedSubviews prop unmounts components that are outside the viewport, reducing main thread work. However, it has known issues on iOS where it can cause blank areas during fast scrolling or when items have varying heights. Test thoroughly on both platforms before enabling.
Incorrect (removeClippedSubviews={true} on iOS without testing):
import { ListItem, Avatar } from '@rneui/themed';
import { FlatList } from 'react-native';
// Bad: Blindly enabling removeClippedSubviews on iOS
// Can cause blank white rectangles during fast scrolling
const ChatList = ({ messages }) => {
return (
<FlatList
data={messages}
renderItem={({ item }) => (
// Variable height content makes issues more likely
<ListItem>
<Avatar source={{ uri: item.avatar }} />
<ListItem.Content>
<ListItem.Title>{item.sender}</ListItem.Title>
{/* Variable height text */}
<ListItem.Subtitle>{item.message}</ListItem.Subtitle>
{item.image && (
<Image source={{ uri: item.image }} style={styles.image} />
)}
</ListItem.Content>
</ListItem>
)}
keyExtractor={(item) => item.id}
// Bad: iOS has rendering bugs with this prop
removeClippedSubviews={true}
/>
);
};Correct (test on both platforms, typically Android-only):
import { ListItem, Avatar } from '@rneui/themed';
import { FlatList, Platform } from 'react-native';
import { useCallback } from 'react';
// Good: Platform-specific configuration with careful consideration
const ChatList = ({ messages }) => {
const renderMessage = useCallback(({ item }) => (
<ListItem>
<Avatar source={{ uri: item.avatar }} />
<ListItem.Content>
<ListItem.Title>{item.sender}</ListItem.Title>
<ListItem.Subtitle>{item.message}</ListItem.Subtitle>
{item.image && (
<Image source={{ uri: item.image }} style={styles.image} />
)}
</ListItem.Content>
</ListItem>
), []);
return (
<FlatList
data={messages}
renderItem={renderMessage}
keyExtractor={(item) => item.id}
// Good: Only enable on Android where it's stable
// Or enable on iOS only after thorough testing
removeClippedSubviews={Platform.OS === 'android'}
/>
);
};
// Good: For fixed-height items on iOS, it's safer to enable
const NotificationList = ({ notifications }) => {
const ITEM_HEIGHT = 72;
return (
<FlatList
data={notifications}
renderItem={({ item }) => (
// Fixed height items are safer with removeClippedSubviews
<ListItem containerStyle={{ height: ITEM_HEIGHT }}>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
<ListItem.Subtitle numberOfLines={1}>
{item.body}
</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
)}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
// Safer on iOS with fixed heights and getItemLayout
removeClippedSubviews={true}
/>
);
};Reference: React Native FlatList Optimization
Extract renderItem with useCallback
Defining renderItem as an inline arrow function creates a new function reference on every parent render, causing FlatList to think the renderItem has changed and potentially re-render all visible items. Extracting the function and wrapping it with useCallback provides a stable reference.
Incorrect (inline arrow function in renderItem prop):
import { ListItem, Avatar } from '@rneui/themed';
import { FlatList } from 'react-native';
// Bad: Inline functions create new references on every render
const UserList = ({ users, onUserSelect }) => {
const [selectedId, setSelectedId] = useState(null);
return (
<FlatList
data={users}
// Bad: New function created every render, destabilizes FlatList
renderItem={({ item }) => (
<ListItem
onPress={() => {
setSelectedId(item.id);
onUserSelect(item);
}}
>
<Avatar source={{ uri: item.avatar }} rounded />
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>{item.role}</ListItem.Subtitle>
</ListItem.Content>
{selectedId === item.id && <ListItem.CheckBox checked />}
</ListItem>
)}
keyExtractor={(item) => item.id}
extraData={selectedId}
/>
);
};Correct (useCallback-wrapped renderItem outside JSX):
import { ListItem, Avatar } from '@rneui/themed';
import { FlatList } from 'react-native';
import { useCallback, useState } from 'react';
const UserList = ({ users, onUserSelect }) => {
const [selectedId, setSelectedId] = useState(null);
// Good: Stable function reference with proper dependencies
const handleUserPress = useCallback((item) => {
setSelectedId(item.id);
onUserSelect(item);
}, [onUserSelect]);
// Good: Extract renderItem with useCallback for stable reference
const renderItem = useCallback(({ item }) => (
<ListItem onPress={() => handleUserPress(item)}>
<Avatar source={{ uri: item.avatar }} rounded />
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>{item.role}</ListItem.Subtitle>
</ListItem.Content>
{selectedId === item.id && <ListItem.CheckBox checked />}
</ListItem>
), [handleUserPress, selectedId]);
return (
<FlatList
data={users}
renderItem={renderItem}
keyExtractor={(item) => item.id}
// extraData ensures re-render when selectedId changes
extraData={selectedId}
/>
);
};Reference: React Native FlatList Optimization
Use FlatList Over ScrollView for Lists
ScrollView renders all children immediately, keeping every item in memory regardless of visibility. For lists with 50+ items, this causes severe memory pressure and janky scrolling. FlatList virtualizes rendering, only mounting items near the viewport, enabling smooth 60fps performance even with thousands of items.
Incorrect (ScrollView with map() for 50+ items):
import { ListItem, Avatar } from '@rneui/themed';
import { ScrollView, View } from 'react-native';
// Bad: All 500 items rendered and kept in memory simultaneously
// Causes memory spikes, slow initial render, janky scrolling
const ProductCatalog = ({ products }) => {
return (
<ScrollView>
{/* Bad: map() renders everything at once */}
{products.map((product) => (
<ListItem key={product.id}>
<Avatar source={{ uri: product.image }} />
<ListItem.Content>
<ListItem.Title>{product.name}</ListItem.Title>
<ListItem.Subtitle>${product.price}</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
))}
</ScrollView>
);
};
// Also bad: Nested ScrollViews with lists
const Dashboard = () => {
return (
<ScrollView>
<Text>Recent Orders</Text>
{/* Bad: Inner ScrollView defeats virtualization */}
<ScrollView>
{orders.map((order) => (
<ListItem key={order.id}>
<ListItem.Title>{order.title}</ListItem.Title>
</ListItem>
))}
</ScrollView>
</ScrollView>
);
};Correct (FlatList with virtualization for any dynamic list):
import { ListItem, Avatar } from '@rneui/themed';
import { FlatList, View } from 'react-native';
import { useCallback } from 'react';
// Good: FlatList only renders visible items + buffer
// Handles 500 or 50,000 items with consistent performance
const ProductCatalog = ({ products }) => {
const renderProduct = useCallback(({ item }) => (
<ListItem>
<Avatar source={{ uri: item.image }} />
<ListItem.Content>
<ListItem.Title>{item.name}</ListItem.Title>
<ListItem.Subtitle>${item.price}</ListItem.Subtitle>
</ListItem.Content>
<ListItem.Chevron />
</ListItem>
), []);
return (
<FlatList
data={products}
renderItem={renderProduct}
keyExtractor={(item) => item.id}
// Additional optimization props
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={11}
/>
);
};
// Good: Use SectionList for grouped data or FlatList with ListHeaderComponent
const Dashboard = ({ orders, headerContent }) => {
const renderOrder = useCallback(({ item }) => (
<ListItem>
<ListItem.Content>
<ListItem.Title>{item.title}</ListItem.Title>
</ListItem.Content>
</ListItem>
), []);
return (
<FlatList
data={orders}
renderItem={renderOrder}
keyExtractor={(item) => item.id}
// Good: Header scrolls with list, no nested ScrollView
ListHeaderComponent={headerContent}
/>
);
};Reference: React Native FlatList Optimization
Configure windowSize for Memory/Performance Balance
The windowSize prop determines how many items FlatList renders outside the visible area (measured in viewport heights). The default of 21 keeps many items in memory, which can cause memory pressure on low-end devices with long lists. Tuning this value balances memory usage against blank content during fast scrolling.
Incorrect (default windowSize=21 for long lists on low-end devices):
import { ListItem, Avatar } from '@rneui/themed';
import { FlatList } from 'react-native';
// Bad: Default windowSize renders ~21 viewports worth of items
// For a 1000-item list, this could be 200+ mounted components
const MessageList = ({ messages }) => {
return (
<FlatList
data={messages} // Could be 1000+ messages
renderItem={({ item }) => (
<ListItem>
<Avatar source={{ uri: item.senderAvatar }} />
<ListItem.Content>
<ListItem.Title>{item.senderName}</ListItem.Title>
<ListItem.Subtitle numberOfLines={2}>
{item.content}
</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
)}
keyExtractor={(item) => item.id}
// No windowSize configured - defaults to 21
// On low-end devices, this causes memory issues
/>
);
};Correct (windowSize tuned for device capability):
import { ListItem, Avatar } from '@rneui/themed';
import { FlatList, Platform } from 'react-native';
import { useMemo } from 'react';
// Good: Configure windowSize based on use case and device
const MessageList = ({ messages, isLowEndDevice }) => {
// Smaller windowSize = less memory, more blank content when scrolling fast
// Larger windowSize = more memory, smoother scrolling experience
const windowSize = useMemo(() => {
if (isLowEndDevice) return 5; // Aggressive memory savings
if (messages.length > 500) return 7; // Balance for long lists
return 11; // Default-ish for short lists
}, [isLowEndDevice, messages.length]);
return (
<FlatList
data={messages}
renderItem={({ item }) => (
<ListItem>
<Avatar source={{ uri: item.senderAvatar }} />
<ListItem.Content>
<ListItem.Title>{item.senderName}</ListItem.Title>
<ListItem.Subtitle numberOfLines={2}>
{item.content}
</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
)}
keyExtractor={(item) => item.id}
// Good: Tuned for memory/performance balance
windowSize={windowSize}
// Complementary props for memory optimization
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
/>
);
};Reference: React Native FlatList Optimization
Use Color Prop for Semantic Button Colors
The Button component's color prop provides semantic color options (primary, secondary, success, warning, error) that integrate with your theme and ensure proper contrast ratios. Overriding colors with custom buttonStyle breaks theme consistency, loses semantic meaning, and may result in accessibility issues with contrast ratios.
Incorrect (custom buttonStyle overriding colors):
// Bad: Hardcoded colors that don't respect theme
const BadButtons = () => (
<View>
<Button
title="Submit"
buttonStyle={{ backgroundColor: '#2089dc' }}
/>
<Button
title="Delete"
buttonStyle={{ backgroundColor: '#ff0000' }}
/>
<Button
title="Cancel"
buttonStyle={{ backgroundColor: '#808080' }}
/>
</View>
);
// Bad: Inconsistent color usage across the app
const FormButtons = () => (
<View>
<Button
title="Save"
buttonStyle={{ backgroundColor: '#28a745' }}
/>
{/* Another screen uses different green */}
<Button
title="Confirm"
buttonStyle={{ backgroundColor: '#4CAF50' }}
/>
</View>
);
// Bad: Manual dark mode color handling
const ManualDarkMode = ({ isDarkMode }) => (
<Button
title="Action"
buttonStyle={{
backgroundColor: isDarkMode ? '#1a5276' : '#2089dc',
}}
titleStyle={{
color: isDarkMode ? '#ffffff' : '#ffffff',
}}
/>
);Correct (Button color prop):
import { Button, createTheme, ThemeProvider } from '@rneui/themed';
// Good: Semantic color prop
const SemanticButtons = () => (
<View>
<Button title="Submit" color="primary" />
<Button title="Learn More" color="secondary" />
<Button title="Save" color="success" />
<Button title="Proceed" color="warning" />
<Button title="Delete" color="error" />
</View>
);
// Good: Define semantic colors in theme
const theme = createTheme({
lightColors: {
primary: '#2089dc',
secondary: '#6c757d',
success: '#28a745',
warning: '#ffc107',
error: '#dc3545',
},
darkColors: {
primary: '#4dabf7',
secondary: '#adb5bd',
success: '#51cf66',
warning: '#ffd43b',
error: '#ff6b6b',
},
});
// Buttons automatically adapt to dark mode
const ThemedApp = () => (
<ThemeProvider theme={theme}>
<Button title="Primary Action" color="primary" />
<Button title="Danger" color="error" />
</ThemeProvider>
);
// Good: Combine color prop with type variants
const ButtonVariants = () => (
<View>
{/* Solid with semantic color */}
<Button title="Save" type="solid" color="success" />
{/* Outline inherits color for border and text */}
<Button title="Edit" type="outline" color="primary" />
{/* Clear uses color for text */}
<Button title="Cancel" type="clear" color="secondary" />
{/* Error variants */}
<Button title="Delete" type="solid" color="error" />
<Button title="Remove" type="outline" color="error" />
</View>
);
// Good: Custom color when needed, but still theme-integrated
const CustomColorButton = () => {
const { theme } = useTheme();
return (
<Button
title="Special Action"
buttonStyle={{
backgroundColor: theme.colors.primary,
borderRadius: 20,
}}
/>
);
};Reference: React Native Elements Button
Configure disabledStyle for Visual Feedback
The Button component's disabledStyle and disabledTitleStyle props allow you to provide clear visual feedback when a button is disabled. Without explicit disabled styling, users may not understand why a button isn't responding to their taps, leading to confusion and frustration. Proper disabled styling improves both UX and accessibility.
Incorrect (disabled without visual indication):
// Bad: disabled prop without visual styling
const SubmitButton = ({ canSubmit, onSubmit }) => (
<Button
title="Submit"
onPress={onSubmit}
disabled={!canSubmit}
// No visual indication that button is disabled!
/>
);
// Bad: Only using opacity without proper styling
const BadDisabledButton = ({ disabled, onPress }) => (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
style={{ opacity: disabled ? 0.5 : 1 }}
>
<Text>Submit</Text>
</TouchableOpacity>
);
// Bad: Inconsistent manual disabled handling
const InconsistentButton = ({ disabled }) => (
<Button
title="Save"
disabled={disabled}
buttonStyle={disabled ? { backgroundColor: 'gray' } : {}}
// Title color remains the same - unclear disabled state
/>
);Correct (disabled with disabledStyle and disabledTitleStyle):
import { Button } from '@rneui/themed';
// Good: Clear disabled state with both button and title styling
const SubmitButton = ({ canSubmit, onSubmit }) => (
<Button
title="Submit"
onPress={onSubmit}
disabled={!canSubmit}
disabledStyle={{
backgroundColor: '#E0E0E0',
borderColor: '#BDBDBD',
}}
disabledTitleStyle={{
color: '#9E9E9E',
}}
/>
);
// Good: Outline button with proper disabled styling
const OutlineDisabledButton = ({ disabled, onPress }) => (
<Button
title="Edit"
type="outline"
onPress={onPress}
disabled={disabled}
disabledStyle={{
borderColor: '#E0E0E0',
backgroundColor: 'transparent',
}}
disabledTitleStyle={{
color: '#BDBDBD',
}}
/>
);
// Good: Set defaults via theme for consistency
import { createTheme, ThemeProvider } from '@rneui/themed';
const theme = createTheme({
components: {
Button: {
disabledStyle: {
backgroundColor: '#E0E0E0',
},
disabledTitleStyle: {
color: '#9E9E9E',
},
},
},
});
// All buttons now have consistent disabled styling
const App = () => (
<ThemeProvider theme={theme}>
<Button title="Submit" disabled />
<Button title="Cancel" disabled />
</ThemeProvider>
);Reference: React Native Elements Button
Configure Icon Props Correctly
The Icon component requires explicit size and color props for consistent rendering across different icon sets and theme contexts. Without explicit configuration, icons may appear inconsistently sized, use unexpected colors, or lack proper accessibility labels. Always specify size, color (or use theme-aware configuration), and type for predictable icon rendering.
Incorrect (Icon without explicit configuration):
// Bad: Icon without size - defaults may vary by icon set
const BadIcon = () => (
<Icon name="home" />
// Size varies by icon set, color depends on context
);
// Bad: Inconsistent icon sizing across app
const Navigation = () => (
<View style={styles.nav}>
<Icon name="home" type="material" />
<Icon name="search" type="ionicon" />
<Icon name="user" type="feather" />
{/* Each icon type has different default sizes! */}
</View>
);
// Bad: Hardcoded colors that don't respect theme
const ThemedScreen = () => (
<View>
<Icon name="settings" type="material" color="#000000" size={24} />
{/* Won't adapt to dark mode */}
</View>
);
// Bad: Missing accessibility for interactive icons
const ActionIcon = ({ onPress }) => (
<Icon name="delete" type="material" onPress={onPress} />
// No accessible label for screen readers
);Correct (Icon with proper configuration):
import { Icon, useTheme } from '@rneui/themed';
// Good: Explicit size and color
const GoodIcon = () => (
<Icon name="home" type="material" size={24} color="#2089dc" />
);
// Good: Consistent sizing across icon sets
const Navigation = () => (
<View style={styles.nav}>
<Icon name="home" type="material" size={24} color="#333" />
<Icon name="search" type="ionicon" size={24} color="#333" />
<Icon name="user" type="feather" size={24} color="#333" />
</View>
);
// Good: Theme-aware icon colors
const ThemedIcon = () => {
const { theme } = useTheme();
return (
<Icon
name="settings"
type="material"
size={24}
color={theme.colors.grey0}
/>
);
};
// Good: Accessible interactive icons
const ActionIcon = ({ onPress }) => (
<Icon
name="delete"
type="material"
size={24}
color="#D32F2F"
onPress={onPress}
accessibilityLabel="Delete item"
accessibilityRole="button"
/>
);
// Good: Icon with container styling
const CircleIcon = () => (
<Icon
name="check"
type="material"
size={20}
color="white"
containerStyle={{
backgroundColor: '#4CAF50',
borderRadius: 20,
padding: 8,
}}
/>
);
// Good: Define icon defaults in theme
const theme = createTheme({
components: {
Icon: {
size: 24,
type: 'material',
},
},
});
// Icons inherit defaults from theme
const ThemedApp = () => (
<ThemeProvider theme={theme}>
<Icon name="home" color="#333" />
<Icon name="settings" color="#333" />
</ThemeProvider>
);Reference: React Native Elements Icon
Use errorMessage for Form Validation
The Input component's errorMessage prop provides built-in error display with consistent styling and proper accessibility. It automatically positions the error message below the input, applies theme-consistent error colors, and announces validation errors to screen readers. Using manual Text components for errors leads to inconsistent positioning, styling, and missed accessibility features.
Incorrect (manual error Text component):
// Bad: Manual error handling outside Input
const EmailInput = ({ value, onChange, error }) => (
<View>
<Input
value={value}
onChangeText={onChange}
label="Email"
placeholder="Enter email"
// No built-in error handling
/>
{error && (
<Text style={{ color: 'red', marginTop: -15, marginLeft: 10 }}>
{error}
</Text>
)}
</View>
);
// Bad: Inconsistent error styling across forms
const PasswordInput = ({ value, onChange, error }) => (
<View>
<Input value={value} onChangeText={onChange} label="Password" />
{error && (
<Text style={{ color: '#ff0000', fontSize: 11, paddingLeft: 5 }}>
{error}
</Text>
)}
</View>
);
// Bad: Error state without visual feedback on input
const BadValidation = ({ error }) => (
<View>
<TextInput style={styles.input} />
{error && <Text style={styles.error}>{error}</Text>}
{/* Input doesn't show error state visually */}
</View>
);Correct (Input errorMessage prop):
import { Input } from '@rneui/themed';
// Good: Built-in error display with consistent styling
const EmailInput = ({ value, onChange, error }) => (
<Input
value={value}
onChangeText={onChange}
label="Email"
placeholder="Enter email"
keyboardType="email-address"
errorMessage={error}
/>
);
// Good: Customize error styling while keeping functionality
const PasswordInput = ({ value, onChange, error }) => (
<Input
value={value}
onChangeText={onChange}
label="Password"
secureTextEntry
errorMessage={error}
errorStyle={{
fontSize: 12,
marginTop: 4,
}}
/>
);
// Good: Full form validation with consistent error handling
const LoginForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!email) newErrors.email = 'Email is required';
else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Invalid email format';
if (!password) newErrors.password = 'Password is required';
else if (password.length < 8) newErrors.password = 'Password must be 8+ characters';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return (
<View>
<Input
value={email}
onChangeText={setEmail}
label="Email"
errorMessage={errors.email}
renderErrorMessage={!!errors.email}
/>
<Input
value={password}
onChangeText={setPassword}
label="Password"
secureTextEntry
errorMessage={errors.password}
renderErrorMessage={!!errors.password}
/>
</View>
);
};
// Good: Set error styling globally via theme
const theme = createTheme({
components: {
Input: {
errorStyle: {
color: '#D32F2F',
fontSize: 12,
},
},
},
});Reference: React Native Elements Input
Use Loading Prop for Async Operations
The Button component's loading prop provides a built-in loading spinner with automatic disable behavior, preventing double-taps and race conditions. It also announces loading state to screen readers. Manually managing loading with a disabled prop and separate ActivityIndicator creates inconsistent behavior and misses accessibility announcements.
Incorrect (manual loading management):
// Bad: Manual loading state handling
const SubmitButton = ({ onSubmit }) => {
const [isLoading, setIsLoading] = useState(false);
const handlePress = async () => {
setIsLoading(true);
await onSubmit();
setIsLoading(false);
};
return (
<TouchableOpacity
onPress={handlePress}
disabled={isLoading}
style={[styles.button, isLoading && styles.disabled]}
>
{isLoading ? (
<ActivityIndicator color="#fff" size="small" />
) : (
<Text style={styles.text}>Submit</Text>
)}
</TouchableOpacity>
);
};
// Another bad pattern: mixing RNE Button with manual ActivityIndicator
const BadButton = ({ isLoading, onPress }) => (
<Button
title={isLoading ? '' : 'Submit'}
onPress={onPress}
disabled={isLoading}
icon={isLoading ? <ActivityIndicator color="#fff" /> : undefined}
/>
);Correct (Button loading prop):
import { Button } from '@rneui/themed';
// Good: Built-in loading state with accessibility
const SubmitButton = ({ onSubmit }) => {
const [isLoading, setIsLoading] = useState(false);
const handlePress = async () => {
setIsLoading(true);
await onSubmit();
setIsLoading(false);
};
return (
<Button
title="Submit"
onPress={handlePress}
loading={isLoading}
/>
);
};
// Customize loading spinner appearance
const CustomLoadingButton = ({ isLoading, onPress }) => (
<Button
title="Save Changes"
onPress={onPress}
loading={isLoading}
loadingProps={{
size: 'small',
color: 'white',
animating: true,
}}
/>
);
// Loading with icon - icon is replaced by spinner
const IconLoadingButton = ({ isSaving, onSave }) => (
<Button
title="Save"
onPress={onSave}
loading={isSaving}
icon={{ name: 'save', type: 'material', color: 'white' }}
loadingStyle={{ marginRight: 10 }}
/>
);Reference: React Native Elements Button
Show Loading State in SearchBar
The SearchBar component's showLoading prop displays a loading indicator during async search operations, providing essential user feedback. Without loading indication, users don't know if their search is being processed, leading to duplicate submissions and poor perceived performance. The built-in loading state integrates with the SearchBar's design without additional layout code.
Incorrect (no loading indication during search):
// Bad: No feedback during async search
const SearchScreen = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = async (text) => {
setQuery(text);
const data = await searchAPI(text);
setResults(data);
// User has no idea search is happening
};
return (
<View>
<SearchBar value={query} onChangeText={handleSearch} />
<FlatList data={results} renderItem={renderItem} />
</View>
);
};
// Bad: Manual loading indicator outside SearchBar
const BadSearch = () => {
const [query, setQuery] = useState('');
const [isLoading, setIsLoading] = useState(false);
return (
<View>
<View style={styles.searchContainer}>
<SearchBar value={query} onChangeText={setQuery} />
{isLoading && <ActivityIndicator style={styles.loader} />}
</View>
{/* Inconsistent positioning, breaks SearchBar layout */}
</View>
);
};Correct (SearchBar showLoading prop):
import { SearchBar } from '@rneui/themed';
// Good: Built-in loading indicator
const SearchScreen = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
const handleSearch = async (text) => {
setQuery(text);
if (text.length > 2) {
setIsSearching(true);
const data = await searchAPI(text);
setResults(data);
setIsSearching(false);
}
};
return (
<View>
<SearchBar
value={query}
onChangeText={handleSearch}
showLoading={isSearching}
placeholder="Search products..."
/>
<FlatList data={results} renderItem={renderItem} />
</View>
);
};
// Good: Customize loading indicator appearance
const CustomLoadingSearch = ({ isSearching, query, onSearch }) => (
<SearchBar
value={query}
onChangeText={onSearch}
showLoading={isSearching}
loadingProps={{
size: 'small',
color: '#2089dc',
}}
placeholder="Search..."
/>
);
// Good: Debounced search with loading state
const DebouncedSearch = () => {
const [query, setQuery] = useState('');
const [isSearching, setIsSearching] = useState(false);
const [results, setResults] = useState([]);
const debounceRef = useRef(null);
const handleSearch = (text) => {
setQuery(text);
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
if (text.length > 2) {
setIsSearching(true);
debounceRef.current = setTimeout(async () => {
const data = await searchAPI(text);
setResults(data);
setIsSearching(false);
}, 300);
} else {
setIsSearching(false);
setResults([]);
}
};
return (
<View>
<SearchBar
value={query}
onChangeText={handleSearch}
showLoading={isSearching}
platform="ios"
placeholder="Search..."
/>
<FlatList data={results} renderItem={renderItem} />
</View>
);
};
// Good: Platform-specific SearchBar with loading
const PlatformSearch = ({ query, onSearch, isLoading }) => (
<SearchBar
value={query}
onChangeText={onSearch}
showLoading={isLoading}
platform={Platform.OS === 'ios' ? 'ios' : 'android'}
cancelButtonTitle="Cancel"
/>
);Reference: React Native Elements SearchBar
Use createTheme for Type-Safe Theme Configuration
The createTheme function provides type safety and proper merging of theme values with defaults. Using plain objects bypasses TypeScript validation, leading to misspelled color keys, invalid component props, and runtime errors that are difficult to debug. createTheme ensures your theme configuration is validated at compile time and properly merged with React Native Elements' default theme.
Incorrect (Plain object theme without createTheme):
import { ThemeProvider } from '@rneui/themed';
// WRONG: Plain object lacks type safety and proper merging
const theme = {
colors: {
primry: '#2089dc', // Typo won't be caught!
secondary: '#ca71eb',
},
Button: {
raisedd: true, // Invalid prop - no compile error!
},
};
const App = () => {
return (
// Theme won't work correctly - missing default values
<ThemeProvider theme={theme}>
{/* Components may have undefined colors */}
</ThemeProvider>
);
};Correct (Using createTheme() with lightColors, darkColors, mode):
import { ThemeProvider, createTheme } from '@rneui/themed';
// CORRECT: createTheme provides type safety and merges with defaults
const theme = createTheme({
lightColors: {
primary: '#2089dc', // TypeScript validates color keys
secondary: '#ca71eb',
background: '#ffffff',
},
darkColors: {
primary: '#1a1a2e',
secondary: '#9c27b0',
background: '#121212',
},
mode: 'light', // Type-checked: only 'light' | 'dark' allowed
components: {
Button: {
raised: true, // Props are validated
buttonStyle: {
borderRadius: 8,
},
},
Text: (props, theme) => ({
// Access to theme object for dynamic styling
style: {
color: theme.colors.grey0,
},
}),
},
});
const App = () => {
return (
<ThemeProvider theme={theme}>
{/* All components properly inherit theme with defaults */}
</ThemeProvider>
);
};Reference: React Native Elements - createTheme
Consistent Imports from @rneui/themed vs @rneui/base
React Native Elements is split into two packages: @rneui/base contains unstyled base components, while @rneui/themed extends them with theming support. Mixing imports causes components to behave inconsistently - some responding to theme changes while others remain static. For themed applications, always import from @rneui/themed to ensure all components access the ThemeProvider context.
Incorrect (Mixing imports from both packages inconsistently):
// WRONG: Mixing imports causes inconsistent theming behavior
import { Button } from '@rneui/themed'; // This will respond to theme
import { Card } from '@rneui/base'; // This won't respond to theme!
import { ListItem } from '@rneui/themed';
import { Avatar } from '@rneui/base'; // Inconsistent with other components
const MyComponent = () => {
return (
<>
{/* Button uses theme colors, Card uses defaults - visual inconsistency */}
<Button title="Themed" />
<Card>
<Avatar /> {/* Won't pick up theme customizations */}
</Card>
</>
);
};Correct (Consistent imports from @rneui/themed for themed apps):
// CORRECT: All imports from @rneui/themed for consistent theming
import {
Button,
Card,
ListItem,
Avatar,
ThemeProvider,
createTheme
} from '@rneui/themed';
const theme = createTheme({
lightColors: {
primary: '#2089dc',
},
});
const MyComponent = () => {
return (
<ThemeProvider theme={theme}>
{/* All components respond consistently to theme changes */}
<Button title="Themed" />
<Card>
<Avatar /> {/* Properly picks up theme customizations */}
</Card>
</ThemeProvider>
);
};Reference: React Native Elements - Installation
Configure SafeAreaProvider for Notched Devices
SafeAreaProvider is a required peer dependency for React Native Elements. Without it, components like BottomSheet, Header, and others cannot properly calculate safe area insets, causing UI elements to be obscured by device notches, status bars, or home indicators. This affects the majority of modern smartphones and leads to poor user experience on flagship devices.
Incorrect (Missing SafeAreaProvider wrapper):
import { ThemeProvider, createTheme, Header, BottomSheet } from '@rneui/themed';
import { View } from 'react-native';
const theme = createTheme({});
// WRONG: Missing SafeAreaProvider
const App = () => {
return (
<ThemeProvider theme={theme}>
<View style={{ flex: 1 }}>
{/* Header will be covered by device notch/status bar */}
<Header centerComponent={{ text: 'My App' }} />
{/* BottomSheet may be hidden behind home indicator */}
<BottomSheet isVisible={true}>
{/* Content */}
</BottomSheet>
</View>
</ThemeProvider>
);
};Correct (SafeAreaProvider at app root with proper configuration):
import { ThemeProvider, createTheme, Header, BottomSheet } from '@rneui/themed';
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context';
import { View } from 'react-native';
const theme = createTheme({});
// CORRECT: SafeAreaProvider wraps the entire app at the root
const App = () => {
return (
// initialWindowMetrics improves initial render on iOS
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<ThemeProvider theme={theme}>
<View style={{ flex: 1 }}>
{/* Header properly respects notch and status bar */}
<Header centerComponent={{ text: 'My App' }} />
{/* BottomSheet respects home indicator */}
<BottomSheet isVisible={true}>
{/* Content */}
</BottomSheet>
</View>
</ThemeProvider>
</SafeAreaProvider>
);
};
export default App;Reference: React Native Elements - Installation
Wrap App with ThemeProvider
ThemeProvider is essential for React Native Elements to function correctly with consistent styling. Without it, components lose access to the theme context, requiring manual prop passing for every style customization. This leads to inconsistent UI, duplicated styling code, and inability to implement features like dark mode switching.
Incorrect (Components without ThemeProvider wrapper):
import { Button, Text } from '@rneui/themed';
import { View } from 'react-native';
// Missing ThemeProvider - components will use default styles only
// No way to customize theme or switch between light/dark modes
const App = () => {
return (
<View>
{/* These components can't access custom theme */}
<Button title="Click Me" />
<Text>Hello World</Text>
</View>
);
};
export default App;Correct (App wrapped with ThemeProvider from @rneui/themed):
import { ThemeProvider, Button, Text, createTheme } from '@rneui/themed';
import { View } from 'react-native';
// Create theme configuration
const theme = createTheme({
lightColors: {
primary: '#2089dc',
},
darkColors: {
primary: '#121212',
},
mode: 'light',
});
// Wrap entire app with ThemeProvider at the root
const App = () => {
return (
<ThemeProvider theme={theme}>
<View>
{/* All components now have access to theme context */}
<Button title="Click Me" />
<Text>Hello World</Text>
</View>
</ThemeProvider>
);
};
export default App;Reference: React Native Elements - ThemeProvider
Enable Proper Tree-Shaking with Direct Imports
When bundle size is critical, using named imports from the package root can include more code than necessary in your bundle. Metro bundler and other bundlers may not always tree-shake effectively from barrel exports. Direct imports from specific component paths guarantee only the required component code is included, potentially reducing bundle size by 30-50% for apps using only a few components.
Incorrect (Barrel imports may include unused code):
// WRONG for bundle-critical apps: Barrel export may pull in all components
import { Button, Card } from '@rneui/themed';
// Even though we only use Button and Card, the bundler might include
// code for Tooltip, Avatar, ListItem, etc. depending on tree-shaking support
const MyScreen = () => {
return (
<>
<Button title="Submit" />
<Card>
<Card.Title>My Card</Card.Title>
</Card>
</>
);
};Correct (Direct component imports when bundle size is critical):
// CORRECT: Direct imports guarantee minimal bundle size
import Button from '@rneui/themed/dist/Button';
import Card from '@rneui/themed/dist/Card';
import { ThemeProvider, createTheme } from '@rneui/themed';
// Only Button and Card code is included in the bundle
// Other components like Tooltip, Avatar, etc. are excluded
const theme = createTheme({
lightColors: {
primary: '#2089dc',
},
});
const MyScreen = () => {
return (
<ThemeProvider theme={theme}>
<Button title="Submit" />
<Card>
<Card.Title>My Card</Card.Title>
</Card>
</ThemeProvider>
);
};
// NOTE: For most apps, the standard import is fine.
// Use direct imports only when bundle analysis shows
// unnecessary code inclusion from @rneui/themedReference: React Native Elements - Installation
Prefer Component-Specific Style Props
React Native Elements components expose specific style props for each internal element (buttonStyle, titleStyle, iconStyle, etc.). Using a single generic style prop or incorrect prop names bypasses TypeScript checking, loses IDE autocomplete benefits, and can result in styles not being applied to the intended element.
Incorrect (single style prop for all component styling):
// Bad: Single style object - unclear which element receives which styles
function ActionButton({ label }: Props) {
return (
<Button
title={label}
icon={{ name: 'check', color: 'white' }}
// Incorrect: Mixing different element styles in one prop
style={{
backgroundColor: '#2089dc',
borderRadius: 8,
fontSize: 18, // This won't work - wrong element
fontWeight: 'bold', // This won't work - wrong element
marginRight: 10, // Intended for icon - won't work
}}
/>
);
}Correct (buttonStyle, titleStyle, containerStyle, etc.):
import { Button } from '@rneui/themed';
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
marginVertical: 8,
},
button: {
backgroundColor: '#2089dc',
borderRadius: 8,
paddingVertical: 12,
},
title: {
fontSize: 18,
fontWeight: 'bold',
},
icon: {
marginRight: 10,
},
});
function ActionButton({ label }: Props) {
return (
<Button
title={label}
icon={{ name: 'check', color: 'white' }}
// Good: Each style prop targets its specific element
containerStyle={styles.container} // Outer wrapper
buttonStyle={styles.button} // TouchableOpacity/Pressable
titleStyle={styles.title} // Text element
iconContainerStyle={styles.icon} // Icon wrapper
/>
);
}Reference: React Native Elements Customization
Use containerStyle for Wrapper Styling
React Native Elements components have a specific styling hierarchy where containerStyle targets the outer wrapper View. Using the generic style prop or applying wrapper styles to internal elements can conflict with the component's internal layout logic, causing unexpected positioning issues or broken alignments.
Incorrect (style prop on RNE components):
// Bad: style prop may conflict with internal component structure
function ProfileCard() {
return (
<Card
// Incorrect: Generic style prop - unclear which element receives these styles
style={{ margin: 16, shadowOpacity: 0.3 }}
>
<Card.Title
// Incorrect: Applying layout styles to title element
style={{ padding: 20, alignSelf: 'stretch' }}
>
Profile
</Card.Title>
</Card>
);
}Correct (containerStyle for outer wrapper, specific props for internals):
import { Card } from '@rneui/themed';
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
cardContainer: {
margin: 16,
shadowOpacity: 0.3,
},
titleWrapper: {
paddingVertical: 10,
},
});
function ProfileCard() {
return (
<Card
// Good: containerStyle explicitly targets the outer wrapper
containerStyle={styles.cardContainer}
>
<Card.Title
// Good: Use component's designated wrapper style prop
containerStyle={styles.titleWrapper}
>
Profile
</Card.Title>
</Card>
);
}Reference: React Native Elements Customization
Use StyleSheet.create Over Inline Objects
Inline style objects are recreated on every render, causing React Native to perform unnecessary style comparisons and potentially triggering child re-renders. StyleSheet.create creates static references that remain stable across renders, improving performance and enabling React Native's style caching optimizations.
Incorrect (inline style objects):
// Bad: Creates new object on every render
function MyComponent() {
return (
<Button
title="Submit"
// New object created each render - triggers style recalculation
buttonStyle={{ padding: 10, margin: 5, backgroundColor: '#2089dc' }}
titleStyle={{ fontSize: 16, fontWeight: 'bold' }}
/>
);
}Correct (StyleSheet.create outside component):
import { StyleSheet } from 'react-native';
import { Button } from '@rneui/themed';
// Good: Static references created once at module load
const styles = StyleSheet.create({
button: {
padding: 10,
margin: 5,
backgroundColor: '#2089dc',
},
title: {
fontSize: 16,
fontWeight: 'bold',
},
});
function MyComponent() {
return (
<Button
title="Submit"
buttonStyle={styles.button}
titleStyle={styles.title}
/>
);
}Reference: React Native Elements Customization
Use Theme Colors Over Hardcoded Values
Hardcoding hex colors in StyleSheet creates maintenance burden and breaks dark mode support. React Native Elements' theming system provides semantic color tokens that automatically adapt to light/dark mode and can be updated globally. Using theme colors ensures visual consistency and simplifies future design system changes.
Incorrect (hardcoded hex colors in StyleSheet):
import { StyleSheet } from 'react-native';
// Bad: Hardcoded colors don't respond to theme changes
const styles = StyleSheet.create({
container: {
backgroundColor: '#ffffff', // Won't change in dark mode
},
title: {
color: '#2089dc', // Hardcoded primary - inconsistent if brand changes
},
subtitle: {
color: '#86939e', // Hardcoded grey - unclear semantic meaning
},
errorText: {
color: '#ff190c', // Hardcoded error color
},
});Correct (theme.colors from useTheme):
import { StyleSheet } from 'react-native';
import { useTheme, Text } from '@rneui/themed';
function ThemedComponent() {
const { theme } = useTheme();
// Good: Dynamic styles using theme colors
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background, // Adapts to dark mode
},
title: {
color: theme.colors.primary, // Consistent with app theme
},
subtitle: {
color: theme.colors.grey3, // Semantic grey scale
},
errorText: {
color: theme.colors.error, // Semantic error color
},
});
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome</Text>
<Text style={styles.subtitle}>Subtitle text</Text>
</View>
);
}Reference: React Native Elements Customization
Memoize Dynamic Styles with useMemo
When styles depend on props or state, computing them directly in the render function creates new object references every render. This defeats React's reconciliation optimizations and can cause child components to re-render unnecessarily. Using useMemo ensures dynamic styles are only recalculated when their dependencies change.
Incorrect (computing styles in render):
// Bad: Style object recreated on every render
function DynamicButton({ isActive, size }: Props) {
// New object created each render, even if isActive/size unchanged
const buttonStyle = {
backgroundColor: isActive ? '#2089dc' : '#ccc',
padding: size === 'large' ? 20 : 10,
opacity: isActive ? 1 : 0.6,
};
return (
<Button
title="Action"
buttonStyle={buttonStyle}
/>
);
}Correct (useMemo for dynamic styles with proper deps):
import { useMemo } from 'react';
import { Button } from '@rneui/themed';
function DynamicButton({ isActive, size }: Props) {
// Good: Style only recalculated when isActive or size changes
const buttonStyle = useMemo(
() => ({
backgroundColor: isActive ? '#2089dc' : '#ccc',
padding: size === 'large' ? 20 : 10,
opacity: isActive ? 1 : 0.6,
}),
[isActive, size]
);
return (
<Button
title="Action"
buttonStyle={buttonStyle}
/>
);
}Reference: React Native Elements Customization
Sync with System Color Scheme
Syncing your app's theme with the system color scheme respects user preferences and provides a native feel. Ignoring useColorScheme from React Native means users who prefer dark mode will see a jarring light theme, harming user experience and accessibility.
Incorrect (ignoring system color scheme):
// Bad: Hardcoded mode ignores user preferences
const theme = createTheme({
mode: 'light', // Always light, even if user prefers dark
});
const App = () => (
<ThemeProvider theme={theme}>
<MyApp />
</ThemeProvider>
);
// Bad: Manual toggle without system sync
const App = () => {
const [isDark, setIsDark] = useState(false);
return (
<ThemeProvider theme={createTheme({ mode: isDark ? 'dark' : 'light' })}>
<MyApp />
</ThemeProvider>
);
};Correct (useEffect with setMode based on system preference):
import { useColorScheme } from 'react-native';
import { createTheme, ThemeProvider, useTheme } from '@rneui/themed';
const theme = createTheme({
lightColors: {
primary: '#6200ee',
},
darkColors: {
primary: '#bb86fc',
},
});
const App = () => (
<ThemeProvider theme={theme}>
<ThemeSync />
<MyApp />
</ThemeProvider>
);
// Component that syncs with system preference
const ThemeSync = () => {
const colorScheme = useColorScheme();
const { updateTheme } = useTheme();
useEffect(() => {
// Automatically switch mode when system preference changes
updateTheme({ mode: colorScheme === 'dark' ? 'dark' : 'light' });
}, [colorScheme, updateTheme]);
return null;
};Reference: React Native Elements Customization
Set Component Defaults in Theme
Defining component defaults in your theme configuration eliminates repetitive props and ensures consistent styling throughout your application. Passing the same props to every Button, Input, or other component creates maintenance burden and inconsistency risks.
Incorrect (repeating props on every component):
// Bad: Same props repeated everywhere
const Screen1 = () => (
<View>
<Button
radius="lg"
color="primary"
titleStyle={{ fontWeight: 'bold' }}
/>
<Input
inputContainerStyle={{ borderBottomWidth: 2 }}
labelStyle={{ color: '#666' }}
/>
</View>
);
const Screen2 = () => (
<View>
<Button
radius="lg"
color="primary"
titleStyle={{ fontWeight: 'bold' }}
/>
<Input
inputContainerStyle={{ borderBottomWidth: 2 }}
labelStyle={{ color: '#666' }}
/>
</View>
);Correct (setting defaults in createTheme components object):
import { createTheme, ThemeProvider } from '@rneui/themed';
const theme = createTheme({
components: {
Button: {
radius: 'lg',
color: 'primary',
titleStyle: {
fontWeight: 'bold',
},
},
Input: {
inputContainerStyle: {
borderBottomWidth: 2,
},
labelStyle: {
color: '#666',
},
},
},
});
// Components automatically inherit defaults
const Screen1 = () => (
<View>
<Button title="Submit" />
<Input label="Email" />
</View>
);
const Screen2 = () => (
<View>
<Button title="Continue" />
<Input label="Password" />
</View>
);Reference: React Native Elements Customization
Extend Theme with Custom Colors Safely
Extending the Colors interface with TypeScript declaration merging ensures your custom colors are type-safe and available with autocomplete throughout your application. Adding arbitrary colors without type declarations leads to runtime errors and no IDE support.
Incorrect (adding colors without type declaration):
// Bad: Custom colors without TypeScript support
const theme = createTheme({
lightColors: {
primary: '#6200ee',
// @ts-ignore - TypeScript doesn't know about brandPurple
brandPurple: '#9c27b0',
surfaceVariant: '#e7e0ec',
},
});
// No autocomplete, potential typos go unnoticed
const BrandCard = () => {
const { theme } = useTheme();
// TypeScript error or any type
return <View style={{ backgroundColor: theme.colors.brandPurple }} />;
};Correct (extending Colors interface and using in createTheme):
import { createTheme, ThemeProvider, Colors } from '@rneui/themed';
// Extend the Colors interface with custom colors
declare module '@rneui/themed' {
export interface Colors {
brandPurple: string;
surfaceVariant: string;
onSurfaceVariant: string;
}
}
// Custom colors are now type-safe
const theme = createTheme({
lightColors: {
primary: '#6200ee',
brandPurple: '#9c27b0',
surfaceVariant: '#e7e0ec',
onSurfaceVariant: '#49454f',
},
darkColors: {
primary: '#bb86fc',
brandPurple: '#ce93d8',
surfaceVariant: '#49454f',
onSurfaceVariant: '#cac4d0',
},
});
// Full TypeScript support with autocomplete
const BrandCard = () => {
const { theme } = useTheme();
return (
<View style={{ backgroundColor: theme.colors.surfaceVariant }}>
<Text style={{ color: theme.colors.onSurfaceVariant }}>
Content
</Text>
<View style={{ backgroundColor: theme.colors.brandPurple }} />
</View>
);
};Reference: React Native Elements Customization
Configure Light/Dark Mode with createTheme
Using createTheme with lightColors and darkColors ensures consistent theming across your entire application. Manual color switching in each component leads to inconsistencies, bugs, and unmaintainable code.
Incorrect (manual color switching in components):
// Bad: Each component handles dark mode independently
const MyComponent = ({ isDarkMode }) => {
const backgroundColor = isDarkMode ? '#121212' : '#ffffff';
const textColor = isDarkMode ? '#ffffff' : '#000000';
return (
<View style={{ backgroundColor }}>
<Text style={{ color: textColor }}>Hello</Text>
<Button
buttonStyle={{ backgroundColor: isDarkMode ? '#bb86fc' : '#6200ee' }}
/>
</View>
);
};Correct (centralized light/dark configuration in createTheme):
import { createTheme, ThemeProvider } from '@rneui/themed';
// Define theme with both light and dark colors
const theme = createTheme({
lightColors: {
primary: '#6200ee',
background: '#ffffff',
},
darkColors: {
primary: '#bb86fc',
background: '#121212',
},
mode: 'light', // or 'dark'
});
// App-level provider
const App = () => (
<ThemeProvider theme={theme}>
<MyApp />
</ThemeProvider>
);
// Component automatically uses correct colors
const MyComponent = () => {
const { theme } = useTheme();
return (
<View style={{ backgroundColor: theme.colors.background }}>
<Button color="primary" />
</View>
);
};Reference: React Native Elements Customization
Use updateTheme for Runtime Changes
The updateTheme function from useTheme allows you to modify theme values at runtime without causing a full re-render or losing component state. Recreating the entire theme object and remounting ThemeProvider causes unnecessary re-renders and can reset component state.
Incorrect (recreating theme and remounting provider):
// Bad: Recreating theme causes full remount
const App = () => {
const [themeConfig, setThemeConfig] = useState({
colors: { primary: '#6200ee' }
});
const changePrimaryColor = (color) => {
// This recreates the entire theme object
setThemeConfig({
...themeConfig,
colors: { ...themeConfig.colors, primary: color }
});
};
// ThemeProvider remounts all children when theme object changes
return (
<ThemeProvider theme={createTheme(themeConfig)}>
<MyApp onColorChange={changePrimaryColor} />
</ThemeProvider>
);
};Correct (using updateTheme from useTheme hook):
import { createTheme, ThemeProvider, useTheme } from '@rneui/themed';
const theme = createTheme({
lightColors: {
primary: '#6200ee',
},
});
const App = () => (
<ThemeProvider theme={theme}>
<MyApp />
</ThemeProvider>
);
// Child component updates theme without remounting
const ThemeControls = () => {
const { updateTheme } = useTheme();
const changePrimaryColor = (color) => {
// Merges changes into existing theme smoothly
updateTheme({
lightColors: {
primary: color,
},
});
};
const toggleDarkMode = () => {
updateTheme((prevTheme) => ({
mode: prevTheme.mode === 'dark' ? 'light' : 'dark',
}));
};
return (
<View>
<Button title="Blue Theme" onPress={() => changePrimaryColor('#2196F3')} />
<Button title="Toggle Dark Mode" onPress={toggleDarkMode} />
</View>
);
};Reference: React Native Elements Customization
Access Theme with useTheme Hook
Using the useTheme hook ensures your components react to theme changes automatically and provides full TypeScript support for theme values. Importing colors directly or hardcoding values breaks reactivity and makes theme switching impossible.
Incorrect (hardcoding colors or direct imports):
// Bad: Hardcoded color values
const MyComponent = () => {
return (
<View style={{ backgroundColor: '#2089dc' }}>
<Text style={{ color: '#ffffff' }}>Hello</Text>
</View>
);
};
// Bad: Importing from a static colors file
import { colors } from './colors';
const MyComponent = () => {
return (
<View style={{ backgroundColor: colors.primary }}>
<Text style={{ color: colors.white }}>Hello</Text>
</View>
);
};Correct (using useTheme hook for dynamic access):
import { useTheme } from '@rneui/themed';
const MyComponent = () => {
// Access theme reactively with full TypeScript support
const { theme } = useTheme();
return (
<View style={{ backgroundColor: theme.colors.primary }}>
<Text style={{ color: theme.colors.white }}>Hello</Text>
</View>
);
};Reference: React Native Elements Customization
Related skills
FAQ
What does react-native-elements do?
react-native-elements: A skill for development. This provides functionality for development workflows.
When should I use react-native-elements?
When you need to use react-native-elements for development tasks, or when react-native-elements: a skill for development. this provides functionality for development workflows.
What are the main capabilities?
react-native-elements.