React Native Components
Use this skill when building user interfaces with React Native's core components and creating custom reusable components.
Key Concepts
Core Components
React Native provides platform-agnostic components that map to native views:
import React from 'react'; import { View, Text, Image, ScrollView, TextInput, TouchableOpacity, SafeAreaView, } from 'react-native';
export default function App() { return ( <SafeAreaView style={{ flex: 1 }}> <ScrollView> <View> <Text>Hello, React Native!</Text> <Image source={{ uri: 'https://example.com/image.jpg' }} style={{ width: 200, height: 200 }} /> <TextInput placeholder="Enter text" style={{ borderWidth: 1, padding: 10 }} /> <TouchableOpacity onPress={() => console.log('Pressed')}> <Text>Press Me</Text> </TouchableOpacity> </View> </ScrollView> </SafeAreaView> ); }
View Component
The fundamental building block:
import { View } from 'react-native';
function Container({ children }: { children: React.ReactNode }) { return ( <View style={{ flex: 1, padding: 16, backgroundColor: '#fff', }}> {children} </View> ); }
Text Component
All text must be wrapped in <Text> :
import { Text } from 'react-native';
function Heading({ children }: { children: string }) { return ( <Text style={{ fontSize: 24, fontWeight: 'bold', color: '#333', }}> {children} </Text> ); }
function Body({ children }: { children: string }) { return ( <Text style={{ fontSize: 16, lineHeight: 24, color: '#666', }}> {children} </Text> ); }
Image Component
Display images from various sources:
import { Image } from 'react-native';
// Remote image <Image source={{ uri: 'https://example.com/image.jpg' }} style={{ width: 200, height: 200 }} />
// Local image <Image source={require('./assets/logo.png')} style={{ width: 100, height: 100 }} />
// With resize mode <Image source={{ uri: 'https://example.com/image.jpg' }} style={{ width: 200, height: 200 }} resizeMode="cover" />
Best Practices
Use SafeAreaView for iOS Notch
Always use SafeAreaView to handle safe areas:
import { SafeAreaView } from 'react-native';
export default function App() { return ( <SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}> {/* Your content */} </SafeAreaView> ); }
FlatList for Long Lists
Use FlatList instead of ScrollView for performance:
import { FlatList, Text, View } from 'react-native';
interface Item { id: string; title: string; }
function ItemList({ items }: { items: Item[] }) { return ( <FlatList data={items} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <View style={{ padding: 16 }}> <Text>{item.title}</Text> </View> )} // Performance optimizations removeClippedSubviews={true} maxToRenderPerBatch={10} updateCellsBatchingPeriod={50} initialNumToRender={10} windowSize={10} /> ); }
Touchable Components
Use appropriate touchables for platform:
import { TouchableOpacity, TouchableHighlight, Pressable } from 'react-native';
// Modern approach - Pressable (recommended) <Pressable onPress={() => console.log('Pressed')} style={({ pressed }) => [ { padding: 12, backgroundColor: pressed ? '#ddd' : '#fff' } ]}
{({ pressed }) => ( <Text style={{ color: pressed ? '#000' : '#333' }}>Press Me</Text> )} </Pressable>
// TouchableOpacity - simple fade effect <TouchableOpacity onPress={() => console.log('Pressed')} activeOpacity={0.7}
<Text>Press Me</Text> </TouchableOpacity>
Component Composition
Build complex UIs from simple components:
import React from 'react'; import { View, Text, StyleSheet } from 'react-native';
interface CardProps { title: string; subtitle?: string; children?: React.ReactNode; }
function Card({ title, subtitle, children }: CardProps) { return ( <View style={styles.card}> <View style={styles.header}> <Text style={styles.title}>{title}</Text> {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>} </View> {children && <View style={styles.content}>{children}</View>} </View> ); }
const styles = StyleSheet.create({ card: { backgroundColor: '#fff', borderRadius: 8, padding: 16, marginVertical: 8, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, header: { marginBottom: 12, }, title: { fontSize: 18, fontWeight: 'bold', color: '#333', }, subtitle: { fontSize: 14, color: '#666', marginTop: 4, }, content: { marginTop: 8, }, });
export default Card;
Common Patterns
List with Pull-to-Refresh
import React, { useState, useCallback } from 'react'; import { FlatList, RefreshControl, Text, View } from 'react-native';
interface Item { id: string; title: string; }
function RefreshableList({ items, onRefresh }: { items: Item[]; onRefresh: () => Promise<void>; }) { const [refreshing, setRefreshing] = useState(false);
const handleRefresh = useCallback(async () => { setRefreshing(true); await onRefresh(); setRefreshing(false); }, [onRefresh]);
return ( <FlatList data={items} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <View style={{ padding: 16 }}> <Text>{item.title}</Text> </View> )} refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={handleRefresh} /> } /> ); }
Infinite Scroll List
import React from 'react'; import { FlatList, ActivityIndicator, View } from 'react-native';
interface Item { id: string; title: string; }
function InfiniteList({ items, loading, onEndReached }: { items: Item[]; loading: boolean; onEndReached: () => void; }) { return ( <FlatList data={items} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <View style={{ padding: 16 }}> <Text>{item.title}</Text> </View> )} onEndReached={onEndReached} onEndReachedThreshold={0.5} ListFooterComponent={ loading ? ( <View style={{ padding: 16 }}> <ActivityIndicator size="large" /> </View> ) : null } /> ); }
Modal Component
import React from 'react'; import { Modal, View, Text, TouchableOpacity, StyleSheet, } from 'react-native';
interface CustomModalProps { visible: boolean; title: string; children: React.ReactNode; onClose: () => void; }
function CustomModal({ visible, title, children, onClose }: CustomModalProps) { return ( <Modal visible={visible} animationType="slide" transparent={true} onRequestClose={onClose} > <View style={styles.overlay}> <View style={styles.modal}> <View style={styles.header}> <Text style={styles.title}>{title}</Text> <TouchableOpacity onPress={onClose}> <Text style={styles.closeButton}>✕</Text> </TouchableOpacity> </View> <View style={styles.content}>{children}</View> </View> </View> </Modal> ); }
const styles = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center', }, modal: { width: '80%', backgroundColor: '#fff', borderRadius: 12, padding: 20, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, title: { fontSize: 20, fontWeight: 'bold', }, closeButton: { fontSize: 24, color: '#666', }, content: { marginTop: 8, }, });
export default CustomModal;
Form Input Component
import React, { useState } from 'react'; import { View, TextInput, Text, StyleSheet, TextInputProps, } from 'react-native';
interface FormInputProps extends TextInputProps { label: string; error?: string; }
function FormInput({ label, error, ...props }: FormInputProps) { const [isFocused, setIsFocused] = useState(false);
return ( <View style={styles.container}> <Text style={styles.label}>{label}</Text> <TextInput {...props} style={[ styles.input, isFocused && styles.inputFocused, error && styles.inputError, ]} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} /> {error && <Text style={styles.error}>{error}</Text>} </View> ); }
const styles = StyleSheet.create({ container: { marginVertical: 8, }, label: { fontSize: 14, fontWeight: '600', marginBottom: 4, color: '#333', }, input: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, fontSize: 16, }, inputFocused: { borderColor: '#007AFF', }, inputError: { borderColor: '#FF3B30', }, error: { color: '#FF3B30', fontSize: 12, marginTop: 4, }, });
export default FormInput;
Anti-Patterns
Don't Nest ScrollViews
// Bad - Nested ScrollViews cause issues <ScrollView> <ScrollView> <Text>Content</Text> </ScrollView> </ScrollView>
// Good - Use single ScrollView <ScrollView> <View> <Text>Content</Text> </View> </ScrollView>
Don't Use Inline Styles for Static Values
// Bad - Creates new object on every render <View style={{ padding: 16, backgroundColor: '#fff' }}> <Text>Content</Text> </View>
// Good - Use StyleSheet const styles = StyleSheet.create({ container: { padding: 16, backgroundColor: '#fff', }, });
<View style={styles.container}> <Text>Content</Text> </View>
Don't Forget to Set keyExtractor
// Bad - May cause rendering issues <FlatList data={items} renderItem={({ item }) => <Text>{item.title}</Text>} />
// Good - Provide unique key <FlatList data={items} keyExtractor={(item) => item.id} renderItem={({ item }) => <Text>{item.title}</Text>} />
Don't Use Index as Key
// Bad - Index as key causes issues with reordering <FlatList data={items} keyExtractor={(item, index) => index.toString()} renderItem={({ item }) => <Text>{item.title}</Text>} />
// Good - Use unique identifier <FlatList data={items} keyExtractor={(item) => item.id} renderItem={({ item }) => <Text>{item.title}</Text>} />
Related Skills
-
react-native-styling: Styling components with StyleSheet
-
react-native-navigation: Navigation between screens
-
react-native-performance: Optimizing component performance