Use when building React Native UI components with core components, custom components, and component patterns. Covers View, Text, Image, ScrollView, FlatList, and component composition.
Limited to specific tools
Additional assets for this skill
This skill is limited to using the following tools:
name: jutsu-react-native:react-native-components description: Use when building React Native UI components with core components, custom components, and component patterns. Covers View, Text, Image, ScrollView, FlatList, and component composition. allowed-tools:
Use this skill when building user interfaces with React Native's core components and creating custom reusable 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>
);
}
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>
);
}
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>
);
}
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"
/>
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>
);
}
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}
/>
);
}
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>
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;
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} />
}
/>
);
}
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
}
/>
);
}
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;
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;
// Bad - Nested ScrollViews cause issues
<ScrollView>
<ScrollView>
<Text>Content</Text>
</ScrollView>
</ScrollView>
// Good - Use single ScrollView
<ScrollView>
<View>
<Text>Content</Text>
</View>
</ScrollView>
// 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>
// 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>}
/>
// 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>}
/>