Introduction
React Native remains a powerhouse for cross-platform mobile development in 2026. Whether you're building a startup MVP or a enterprise-grade app, mastering prompts that generate clean components, handle navigation gracefully, and wire up APIs efficiently can save you hours of boilerplate. This collection brings you 18 battle-tested prompts I use daily, each with a real usage example and code snippet. No fluff—just practical, working solutions.
Prompts for UI Components
1. Generate a Reusable Button Component
Prompt: "Create a reusable Button component with props for title, onPress, variant (primary/secondary/outline), and disabled. Use TypeScript and StyleSheet."
Example Use: Drop this into a shared components folder to maintain consistent styling across screens.
import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
interface ButtonProps {
title: string;
onPress: () => void;
variant?: 'primary'
| 'secondary' | 'outline';
disabled?: boolean;
}
const Button: React.FC<ButtonProps> = ({ title, onPress, variant = 'primary', disabled = false }) => {
const buttonStyle = [
styles.base,
variant === 'primary' && styles.primary,
variant === 'secondary' && styles.secondary,
variant === 'outline' && styles.outline,
disabled && styles.disabled,
];
return (
<TouchableOpacity onPress={onPress} disabled={disabled} style={buttonStyle}>
<Text style={[styles.text, variant === 'outline' && styles.outlineText]}>{title}</Text>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
base: { padding: 12, borderRadius: 8, alignItems: 'center' },
primary: { backgroundColor: '#007AFF' },
secondary: { backgroundColor: '#5856D6' },
outline: { backgroundColor: 'transparent', borderWidth: 1, borderColor: '#007AFF' },
disabled: { opacity: 0.5 },
text: { color: 'white', fontSize: 16, fontWeight: '600' },
outlineText: { color: '#007AFF' },
});
export default Button;
2. Build a Custom Text Input with Validation
Prompt: "Create a ValidatedInput component that supports label, placeholder, value, onChangeText, and a validators array returning error messages. Show error below input."
Example Use: Use in forms where multiple validation rules are needed (e.g., email format, required).
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';
interface ValidatedInputProps {
label: string;
placeholder: string;
value: string;
onChangeText: (text: string) => void;
validators?: Array<(value: string) => string | null>;
}
const ValidatedInput: React.FC<ValidatedInputProps> = ({ label, placeholder, value, onChangeText, validators }) => {
const [error, setError] = useState<string | null>(null);
const handleChange = (text: string) => {
onChangeText(text);
if (validators) {
for (const validate of validators) {
const errorMsg = validate(text);
if (errorMsg) {
setError(errorMsg);
return;
}
}
setError(null);
}
};
return (
<View style={styles.container}>
<Text style={styles.label}>{label}</Text>
<TextInput
style={[styles.input, error && styles.inputError]}
placeholder={placeholder}
value={value}
onChangeText={handleChange}
/>
{error && <Text style={styles.error}>{error}</Text>}
</View>
);
};
const styles = StyleSheet.create({
container: { marginVertical: 8 },
label: { fontSize: 14, marginBottom: 4, color: '#333' },
input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 6, padding: 10, fontSize: 16 },
inputError: { borderColor: 'red' },
error: { color: 'red', fontSize: 12, marginTop: 2 },
});
export default ValidatedInput;
3. Create a FlatList with Pull-to-Refresh and Infinite Scroll
Prompt: "Generate a PaginatedList component that fetches data from a given API endpoint, supports pull-to-refresh, and loads more items on scroll end. Show loading indicators."
Example Use: Ideal for feeds, search results, or any scrollable content that needs pagination.
import React, { useState, useEffect, useCallback } from 'react';
import { FlatList, ActivityIndicator, RefreshControl, View, Text, StyleSheet } from 'react-native';
interface PaginatedListProps {
endpoint: string;
renderItem: ({ item }: { item: any }) => JSX.Element;
pageSize?: number;
}
const PaginatedList: React.FC<PaginatedListProps> = ({ endpoint, renderItem, pageSize = 10 }) => {
const [data, setData] = useState<any[]>([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [hasMore, setHasMore] = useState(true);
const fetchData = useCallback(async (pageNum: number, isRefresh = false) => {
setLoading(true);
try {
const response = await fetch(`${endpoint}?page=${pageNum}&limit=${pageSize}`);
const result = await response.json();
if (isRefresh) {
setData(result.items);
} else {
setData(prev => [...prev, ...result.items]);
}
setHasMore(result.items.length === pageSize);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}, [endpoint, pageSize]);
useEffect(() => {
fetchData(1);
}, []);
const onRefresh = useCallback(async () => {
setRefreshing(true);
setPage(1);
await fetchData(1, true);
setRefreshing(false);
}, [fetchData]);
const loadMore = () => {
if (!loading && hasMore) {
const nextPage = page + 1;
setPage(nextPage);
fetchData(nextPage);
}
};
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item, index) => item.id ? item.id.toString() : index.toString()}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? <ActivityIndicator size="small" /> : null}
/>
);
};
const styles = StyleSheet.create({
container: { flex: 1 },
});
export default PaginatedList;
4. Build a Modal Overlay with Animation
Prompt: "Create a ModalOverlay component that slides in from the bottom with an animated backdrop. Accept visible, onClose, and children props. Use Animated API."
Example Use: For confirmation dialogs, bottom sheets, or custom alerts.
import React, { useEffect, useRef } from 'react';
import { Animated, TouchableOpacity, View, StyleSheet, Dimensions } from 'react-native';
interface ModalOverlayProps {
visible: boolean;
onClose: () => void;
children: React.ReactNode;
}
const ModalOverlay: React.FC<ModalOverlayProps> = ({ visible, onClose, children }) => {
const translateY = useRef(new Animated.Value(Dimensions.get('window').height)).current;
useEffect(() => {
if (visible) {
Animated.timing(translateY, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
} else {
Animated.timing(translateY, {
toValue: Dimensions.get('window').height,
duration: 300,
useNativeDriver: true,
}).start();
}
}, [visible]);
if (!visible) return null;
return (
<View style={styles.overlay}>
<TouchableOpacity style={styles.backdrop} onPress={onClose} />
<Animated.View style={[styles.modal, { transform: [{ translateY }] }]}>
{children}
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
overlay: { flex: 1, justifyContent: 'flex-end' },
backdrop: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)' },
modal: { backgroundColor: 'white', borderTopLeftRadius: 16, borderTopRightRadius: 16, padding: 20 },
});
export default ModalOverlay;
5. Create a Horizontal Scrollable Tab Bar
Prompt: "Build a TabBar component with horizontally scrollable tabs. Each tab has a label, active state with underline, and onTabPress callback. Use ScrollView."
Example Use: Product categories in an e-commerce app or sections in a profile screen.
import React, { useRef } from 'react';
import { ScrollView, TouchableOpacity, Text, View, StyleSheet } from 'react-native';
interface TabBarProps {
tabs: string[];
activeTab: string;
onTabPress: (tab: string) => void;
}
const TabBar: React.FC<TabBarProps> = ({ tabs, activeTab, onTabPress }) => {
const scrollRef = useRef<ScrollView>(null);
return (
<ScrollView
ref={scrollRef}
horizontal
showsHorizontalScrollIndicator={false}
style={styles.container}
>
{tabs.map((tab) => (
<TouchableOpacity
key={tab}
onPress={() => onTabPress(tab)}
style={[styles.tab, activeTab === tab && styles.activeTab]}
>
<Text style={[styles.label, activeTab === tab && styles.activeLabel]}>{tab}</Text>
{activeTab === tab && <View style={styles.underline} />}
</TouchableOpacity>
))}
</ScrollView>
);
};
const styles = StyleSheet.create({
container: { maxHeight: 48 },
tab: { paddingHorizontal: 16, paddingVertical: 12, alignItems: 'center' },
activeTab: { borderBottomWidth: 0 },
label: { fontSize: 14, color: '#666' },
activeLabel: { color: '#007AFF', fontWeight: '600' },
underline: { height: 2, backgroundColor: '#007AFF', marginTop: 4, width: '100%' },
});
export default TabBar;
Prompts for Navigation
6. Set Up Stack Navigator with TypeScript
Prompt: "Define a RootStackParamList with two screens: Home and Details. Home receives no params, Details receives itemId: number and title: string. Create a Stack.Navigator with those screens."
Example Use: Standard navigation setup for a list-detail flow.
import { createNativeStackNavigator } from '@react-navigation/native-stack';
export type RootStackParamList = {
Home: undefined;
Details: { itemId: number; title: string };
};
const Stack = createNativeStackNavigator<RootStackParamList>();
const AppNavigator = () => (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
export default AppNavigator;
7. Add Bottom Tab Navigator with Icons
Prompt: "Create a bottom tab navigator with three tabs: Home, Search, Profile. Use @react-navigation/bottom-tabs and include icons from react-native-vector-icons/MaterialIcons."
Example Use: Standard app shell with navigation between main sections.
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
type TabParamList = {
Home: undefined;
Search: undefined;
Profile: undefined;
};
const Tab = createBottomTabNavigator<TabParamList>();
const TabNavigator = () => (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ color, size }) => {
let iconName: string;
if (route.name === 'Home') iconName = 'home';
else if (route.name === 'Search') iconName = 'search';
else iconName = 'person';
return <MaterialIcons name={iconName} size={size} color={color} />;
},
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
8. Implement Deep Linking for Push Notifications
Prompt: "Configure deep linking in React Navigation to handle URLs like myapp://profile/123. Create a linking config that maps path to the Profile screen with userId param."
Example Use: Open specific screens when user taps a notification.
import { LinkingOptions } from '@react-navigation/native';
const linking: LinkingOptions<RootStackParamList> = {
prefixes: ['myapp://'],
config: {
screens: {
Home: 'home',
Profile: 'profile/:userId',
},
},
};
// Pass linking to NavigationContainer
// <NavigationContainer linking={linking}>
Prompts for API Integration
9. Create a Custom Fetch Hook with Loading and Error States
Prompt: "Build a useFetch hook that takes a URL and options, returns { data, loading, error, refetch }. Handle cancellation on unmount."
Example Use: Centralize data fetching logic across components.
import { useState, useEffect, useCallback, useRef } from 'react';
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
function useFetch<T = any>(url: string, options?: RequestInit): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const abortRef = useRef<AbortController | null>(null);
const fetchData = useCallback(async () => {
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setLoading(true);
setError(null);
try {
const response = await fetch(url, { ...options, signal: controller.signal });
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const result = await response.json();
setData(result);
} catch (err: any) {
if (err.name !== 'AbortError') {
setError(err.message || 'Something went wrong');
}
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => {
fetchData();
return () => abortRef.current?.abort();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
export default useFetch;
10. Handle API Pagination with Cursor-Based Pagination
Prompt: "Write a useCursorPagination hook that fetches items using a cursor parameter. It should expose items, nextCursor, loadMore, loading, and hasMore."
Example Use: For APIs that return cursors instead of page numbers (e.g., GraphQL connections).
import { useState, useCallback } from 'react';
interface CursorPaginationResult<T> {
items: T[];
nextCursor: string | null;
loading: boolean;
loadMore: () => Promise<void>;
hasMore: boolean;
}
function useCursorPagination<T>(
fetchFn: (cursor: string
| null) => Promise<{ items: T[]; nextCursor: string | null }>
): CursorPaginationResult<T> {
const [items, setItems] = useState<T[]>([]);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const loadMore = useCallback(async () => {
if (loading || !hasMore) return;
setLoading(true);
try {
const result = await fetchFn(nextCursor);
setItems(prev => [...prev, ...result.items]);
setNextCursor(result.nextCursor);
setHasMore(result.nextCursor !== null);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}, [fetchFn, nextCursor, loading, hasMore]);
return { items, nextCursor, loading, loadMore, hasMore };
}
export default useCursorPagination;
11. Build a Debounced Search Hook
Prompt: "Create a useDebouncedSearch hook that takes a search function and delay. It should return query, setQuery, results, loading. Debounce the API call."
Example Use: Search bar that queries after user stops typing for 300ms.
import { useState, useEffect, useRef } from 'react';
function useDebouncedSearch<T>(
searchFn: (query: string) => Promise<T>,
delay = 300
) {
const [query, setQuery] = useState('');
const [results, setResults] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const timerRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
if (!query) {
setResults(null);
setLoading(false);
return;
}
setLoading(true);
timerRef.current = setTimeout(async () => {
try {
const data = await searchFn(query);
setResults(data);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}, delay);
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, [query, delay]);
return { query, setQuery, results, loading };
}
export default useDebouncedSearch;
12. Handle API Errors Gracefully
Prompt: "Write an apiErrorHandler utility that catches fetch errors and returns a user-friendly message. Handle network errors, timeouts, and 4xx/5xx status codes."
Example Use: Wrap all API calls to provide consistent error feedback.
interface ApiError {
message: string;
status?: number;
}
async function apiRequest<T>(url: string, options?: RequestInit): Promise<T> {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(url, { ...options, signal: controller.signal });
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw { message: errorData.message || `Request failed with status ${response.status}`, status: response.status };
}
return await response.json();
} catch (error: any) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw { message: 'Request timed out. Please try again.', status: 408 };
}
if (error.message === 'Network request failed') {
throw { message: 'No internet connection. Please check your network.', status: 0 };
}
throw { message: error.message || 'An unexpected error occurred.', status: error.status };
}
}
export default apiRequest;
13. Cache API Responses Locally
Prompt: "Create a useCachedFetch hook that stores responses in AsyncStorage and serves cached data while refetching in background. Has staleTime config."
Example Use: Reduce loading times for data that doesn't change often, like settings.
import { useState, useEffect, useCallback } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface CachedFetchResult<T> {
data: T | null;
loading: boolean;
error: string | null;
}
function useCachedFetch<T>(key: string, fetchFn: () => Promise<T>, staleTime = 60000): CachedFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadData = useCallback(async () => {
try {
const cached = await AsyncStorage.getItem(key);
if (cached) {
const { data: cachedData, timestamp } = JSON.parse(cached);
if (Date.now() - timestamp < staleTime) {
setData(cachedData);
setLoading(false);
return;
}
}
} catch (e) {
// ignore cache read errors
}
setLoading(true);
try {
const freshData = await fetchFn();
await AsyncStorage.setItem(key, JSON.stringify({ data: freshData, timestamp: Date.now() }));
setData(freshData);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
}, [key, fetchFn, staleTime]);
useEffect(() => {
loadData();
}, [loadData]);
return { data, loading, error };
}
export default useCachedFetch;
14. Manage Authentication State with Context
Prompt: "Create an AuthContext with user, login, logout, and loading state. Use axios interceptors to attach tokens to requests."
Example Use: Centralize auth logic and protect API calls.
import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AuthContextType {
user: any | null;
loading: boolean;
login: (token: string, userData: any) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType>({} as AuthContextType);
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<any | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const bootstrap = async () => {
const token = await AsyncStorage.getItem('authToken');
if (token) {
// Validate token with API or decode
setUser({ token });
}
setLoading(false);
};
bootstrap();
}, []);
const login = async (token: string, userData: any) => {
await AsyncStorage.setItem('authToken', token);
setUser(userData);
};
const logout = async () => {
await AsyncStorage.removeItem('authToken');
setUser(null);
};
return (
<AuthContext.Provider value={{ user, loading, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
15. Create a Retry Logic for Failed API Calls
Prompt: "Write a retryFetch function that retries a failed API call up to 3 times with exponential backoff (1s, 2s, 4s). Only retry on network errors or 5xx status codes."
Example Use: Improve resilience for flaky connections.
async function retryFetch<T>(
url: string,
options?: RequestInit,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
let lastError: any;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) {
return await response.json();
}
if (response.status < 500) {
// 4xx errors are not retryable
throw new Error(`HTTP error: ${response.status}`);
}
// 5xx errors: retry
lastError = new Error(`HTTP error: ${response.status}`);
} catch (error: any) {
lastError = error;
if (error.name === 'AbortError') {
throw error;
}
if (attempt < maxRetries - 1) {
const delay = baseDelay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
export default retryFetch;
16. Build a Real-Time Chat with WebSockets
Prompt: "Create a useWebSocket hook that connects to a WebSocket endpoint, handles reconnection with exponential backoff, and returns messages, sendMessage, and connected state."
Example Use: Live chat or notifications.
import { useState, useEffect, useRef, useCallback } from 'react';
interface WebSocketResult {
messages: any[];
sendMessage: (msg: string) => void;
connected: boolean;
}
function useWebSocket(url: string): WebSocketResult {
const [messages, setMessages] = useState<any[]>([]);
const [connected, setConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimer = useRef<NodeJS.Timeout | null>(null);
let reconnectAttempt = 0;
const connect = useCallback(() => {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
setConnected(true);
reconnectAttempt = 0;
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setMessages(prev => [...prev, data]);
};
ws.onclose = () => {
setConnected(false);
const delay = Math.min(1000 * Math.pow(2, reconnectAttempt), 30000);
reconnectTimer.current = setTimeout(() => {
reconnectAttempt++;
connect();
}, delay);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}, [url]);
useEffect(() => {
connect();
return () => {
wsRef.current?.close();
if (reconnectTimer.current) clearTimeout(reconnectTimer.current);
};
}, [connect]);
const sendMessage = (msg: string) => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify({ text: msg }));
}
};
return { messages, sendMessage, connected };
}
export default useWebSocket;
17. Offline-First Data with NetInfo
Prompt: "Write a hook useOnlineStatus that uses @react-native-community/netinfo to track connectivity and expose isOnline. Show a banner when offline."
Example Use: Disable API calls or show cached data when offline.
import { useState, useEffect } from 'react';
import NetInfo from '@react-native-community/netinfo';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsOnline(state.isConnected ?? false);
});
return () => unsubscribe();
}, []);
return isOnline;
}
export default useOnlineStatus;
18. Upload Files with Progress Tracking
Prompt: "Create an uploadFile function using XMLHttpRequest to track upload progress. Accept file URI, upload URL, and return { response, progress }."
Example Use: Image upload with progress bar.
interface UploadResult {
response: any;
progress: number;
}
async function uploadFile(
fileUri: string,
uploadUrl: string,
onProgress?: (progress: number) => void
): Promise<UploadResult> {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', {
uri: fileUri,
type: 'image/jpeg',
name: 'photo.jpg',
} as any);
const xhr = new XMLHttpRequest();
xhr.open('POST', uploadUrl);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded / event.total) * 100);
onProgress?.(progress);
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({ response: JSON.parse(xhr.responseText), progress: 100 });
} else {
reject(new Error(`Upload failed with status ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('Network error during upload'));
xhr.send(formData);
});
}
export default uploadFile;
Conclusion
These 18 prompts cover the core building blocks of any React Native app: reusable UI components, robust navigation, and reliable API communication. Each prompt is designed to be dropped into your project and adapted to your specific needs. Start by integrating the ones that address your current pain points—whether it's repetitive form validation, flaky network calls, or missing pagination. As you build, you'll find that these patterns become second nature, saving you hours of boilerplate and debugging. Now, go open your code editor and turn these prompts into production-ready features.
For more tutorials and advanced patterns, visit asibiont.com/blog.
Comments