React Native has become the go-to framework for cross-platform mobile development, powering apps like Instagram, Shopify, and Uber Eats. But even seasoned developers know the pain of wrestling with component state, bridging native modules, or chasing down performance bottlenecks. What if you could offload the mundane parts to an AI assistant? With the right prompts, you can turn AI from a toy into a powerful pair programmer.
This guide offers 15 concrete prompt patterns that target the entire React Native lifecycle—from generating UI components to profiling and optimizing performance. Each prompt is a template you can copy, adapt, and iterate on. No fluff, just actionable commands that respect React Native's idioms and best practices.
1. Component Scaffolding with TypeScript
Generating a component might be simple, but doing it right requires following the project's conventions. A well-crafted prompt can produce a typed component with proper styles, accessibility props, and test IDs.
Prompt:
Create a functional React Native component named `ProductCard` using TypeScript. It should accept a `product` object (with `id`, `name`, `price`, `imageUrl`), display the image with a fallback, show the name and price, and include a press handler. Use `StyleSheet.create` for styling, include `accessibilityLabel`, and set `testID` for testing. Assume we're using React 18 and the new JSX transform.
Example Output:
import React from 'react';
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
interface Product {
id: string;
name: string;
price: number;
imageUrl: string;
}
interface ProductCardProps {
product: Product;
onPress: (id: string) => void;
}
const ProductCard: React.FC<ProductCardProps> = ({ product, onPress }) => (
<TouchableOpacity
style={styles.card}
onPress={() => onPress(product.id)}
accessibilityLabel={`${product.name}, ${product.price} dollars`}
testID="product-card"
>
<Image source={{ uri: product.imageUrl }} style={styles.image} />
<Text style={styles.name}>{product.name}</Text>
<Text style={styles.price}>${product.price.toFixed(2)}</Text>
</TouchableOpacity>
);
const styles = StyleSheet.create({
card: { padding: 16, borderRadius: 8, backgroundColor: '#fff' },
image: { width: '100%', height: 150, borderRadius: 8 },
name: { fontSize: 16, fontWeight: 'bold', marginTop: 8 },
price: { fontSize: 14, color: '#666' },
});
export default ProductCard;
Why it works: This prompt specifies the data shape, design tokens, and platform conventions. You can extend it by asking for memoization or dark mode support.
2. Navigation Setup with React Navigation
Setting up a stack navigator with proper types can be error-prone. Use this prompt to generate a typed navigator skeleton.
Prompt:
Generate a React Navigation v6 stack navigator setup with TypeScript. I have three screens: Home, Profile, and Settings. Home takes a `userId` param. Profile takes `userId` and `isOwnProfile`. Settings takes no params. Include the necessary types and a `NavigationContainer` wrapper.
Example Output:
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
export type RootStackParamList = {
Home: { userId: string };
Profile: { userId: string; isOwnProfile: boolean };
Settings: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
Why it works: By specifying the param list, the AI generates type-safe navigation. You can ask for deep linking or auth flows next.
3. State Management with Redux Toolkit
Managing global state with Redux Toolkit? This prompt generates a slice with reducers and selectors.
Prompt:
Create a Redux Toolkit slice for a shopping cart. State has `items: {id: string, quantity: number}[]`. Actions: addItem, removeItem, incrementQuantity, decrementQuantity. Include selectors for totalItemCount and totalPrice (assume items cost $10 each). Use TypeScript and follow the RTK pattern.
Example Output:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface CartItem { id: string; quantity: number; }
interface CartState { items: CartItem[]; }
const initialState: CartState = { items: [] };
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem(state, action: PayloadAction<string>) {
const existing = state.items.find(i => i.id === action.payload);
if (existing) existing.quantity += 1;
else state.items.push({ id: action.payload, quantity: 1 });
},
removeItem(state, action: PayloadAction<string>) {
state.items = state.items.filter(i => i.id !== action.payload);
},
incrementQuantity(state, action: PayloadAction<string>) {
const item = state.items.find(i => i.id === action.payload);
if (item) item.quantity += 1;
},
decrementQuantity(state, action: PayloadAction<string>) {
const item = state.items.find(i => i.id === action.payload);
if (item) {
item.quantity -= 1;
if (item.quantity <= 0) state.items = state.items.filter(i => i.id !== action.payload);
}
},
},
});
export const { addItem, removeItem, incrementQuantity, decrementQuantity } = cartSlice.actions;
export const selectTotalItemCount = (state: RootState) => state.cart.items.reduce((sum, item) => sum + item.quantity, 0);
export const selectTotalPrice = (state: RootState) => state.cart.items.reduce((sum, item) => sum + item.quantity * 10, 0);
export default cartSlice.reducer;
Why it works: The prompt defines state shape and actions, so the AI produces idiomatic RTK code. You can adapt it to other state containers.
4. Async Data Fetching with React Query
React Query simplifies data fetching. Use this prompt to get a custom hook for an API call.
Prompt:
Write a custom hook `useProducts` that fetches products from `/api/products` using React Query v4. The API returns `{id, name, price}[]`. Include loading and error states, and support pagination via a `page` parameter. Use TypeScript.
Example Output:
import { useQuery } from '@tanstack/react-query';
interface Product { id: string; name: string; price: number; }
const fetchProducts = async (page: number): Promise<Product[]> => {
const res = await fetch(`/api/products?page=${page}`);
if (!res.ok) throw new Error('Network response was not ok');
return res.json();
};
export function useProducts(page: number) {
return useQuery({
queryKey: ['products', page],
queryFn: () => fetchProducts(page),
});
}
Why it works: It leverages React Query's caching and invalidation. You can add keepPreviousData or staleTime as needed.
5. Styling with Themes and Dark Mode
Dark mode is a must-have. This prompt generates a theme system that respects system settings.
Prompt:
Create a theme system for React Native using `useColorScheme`. Define a light and dark theme with colors: background, text, primary, secondary. Provide a `useTheme` hook that returns the current theme, and a `ThemedText` component that uses it.
Example Output:
import React from 'react';
import { useColorScheme, Text, TextProps } from 'react-native';
interface Theme { background: string; text: string; primary: string; secondary: string; }
const lightTheme: Theme = { background: '#fff', text: '#000', primary: '#6200ee', secondary: '#03dac6' };
const darkTheme: Theme = { background: '#121212', text: '#fff', primary: '#bb86fc', secondary: '#03dac6' };
export function useTheme(): Theme {
const scheme = useColorScheme();
return scheme === 'dark' ? darkTheme : lightTheme;
}
export const ThemedText: React.FC<TextProps> = ({ style, ...props }) => {
const theme = useTheme();
return <Text style={[{ color: theme.text }, style]} {...props} />;
};
Why it works: It's a minimal, scalable approach. You can extend to include spacing, typography, etc.
6. Form Validation with Formik and Yup
Forms are painful. Generate a form with validation and error messages.
Prompt:
Create a login form using Formik and Yup. Fields: email (required, valid email), password (required, min 6 chars). Display error messages below each field. On submit, call a `login` function. Use TypeScript.
Example Output:
import React from 'react';
import { View, TextInput, Text, Button } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';
const LoginSchema = Yup.object().shape({
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string().min(6, 'Too short').required('Required'),
});
interface LoginValues { email: string; password: string; }
export const LoginForm = () => (
<Formik<LoginValues>
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={(values) => login(values)}
>
{({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (
<View>
<TextInput
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
placeholder="Email"
/>
{touched.email && errors.email && <Text>{errors.email}</Text>}
<TextInput
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
placeholder="Password"
secureTextEntry
/>
{touched.password && errors.password && <Text>{errors.password}</Text>}
<Button onPress={handleSubmit} title="Login" />
</View>
)}
</Formik>
);
Why it works: The prompt specifies the validation rules, so the AI fills in the boilerplate.
7. Animations with Reanimated
Reanimated is powerful but tricky. This prompt generates a simple fade-in animation.
Prompt:
Write a React Native component that fades in a view when it mounts using Reanimated v3. Use `useSharedValue`, `withTiming`, and `useAnimatedStyle`. Include a button to toggle visibility.
Example Output:
import React, { useEffect } from 'react';
import { View, Button } from 'react-native';
import Animated, { useSharedValue, withTiming, useAnimatedStyle } from 'react-native-reanimated';
export const FadeInView = () => {
const opacity = useSharedValue(0);
const [visible, setVisible] = React.useState(true);
useEffect(() => {
opacity.value = visible ? withTiming(1, { duration: 500 }) : withTiming(0, { duration: 500 });
}, [visible]);
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value }));
return (
<View>
<Animated.View style={[animatedStyle, { width: 100, height: 100, backgroundColor: 'blue' }]} />
<Button title="Toggle" onPress={() => setVisible(!visible)} />
</View>
);
};
Why it works: It uses the core primitives correctly. For more complex gestures, specify the gesture handler.
8. Custom Hooks for Common Patterns
Create reusable logic, like a hook for debouncing text input.
Prompt:
Create a custom hook `useDebounce` that takes a value and a delay (in ms) and returns the debounced value. Use TypeScript.
Example Output:
import { useState, useEffect } from 'react';
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
Why it works: This is a classic pattern that you can reuse across components.
9. Testing with Jest and React Native Testing Library
Write unit tests for your components.
Prompt:
Write a Jest and React Native Testing Library test for a `ProductCard` component. It should verify that the product name and price render, and that pressing the card calls the onPress callback with the product id.
Example Output:
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import ProductCard from './ProductCard';
const mockProduct = { id: '1', name: 'Test Product', price: 9.99, imageUrl: 'https://example.com/img.png' };
it('renders product info and handles press', () => {
const onPress = jest.fn();
const { getByText, getByTestId } = render(<ProductCard product={mockProduct} onPress={onPress} />);
expect(getByText('Test Product')).toBeTruthy();
expect(getByText('$9.99')).toBeTruthy();
fireEvent.press(getByTestId('product-card'));
expect(onPress).toHaveBeenCalledWith('1');
});
Why it works: The prompt specifies what to test, so the AI writes assertions accordingly.
10. Performance Profiling with React DevTools
While AI can't run profiling, it can generate code to help you measure performance, like using InteractionManager.
Prompt:
Show how to use `InteractionManager.runAfterInteractions` to defer a heavy operation (e.g., rendering a large list) in React Native. Provide a code example.
Example Output:
import React, { useEffect, useState } from 'react';
import { InteractionManager, FlatList, View, Text } from 'react-native';
const HeavyList = () => {
const [data, setData] = useState([]);
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
// Simulate fetching large data
setData(Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Item ${i}` })));
});
}, []);
return (
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
);
};
Why it works: It's a common optimization to avoid blocking the main thread. You can also ask about memoization and useCallback.
11. Native Modules (Bridging)
Sometimes you need native code. Generate a bridge for a simple native module.
Prompt:
Create a native module in React Native that exposes a method `getDeviceName` that returns a string. Show the Android implementation (in Kotlin) and the iOS implementation (in Swift), plus the JS side.
Example Output:
Android (MyNativeModule.kt):
package com.example.mymodule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import android.os.Build
class MyNativeModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "MyNativeModule"
@ReactMethod
fun getDeviceName() : String {
return Build.MODEL
}
}
iOS (MyNativeModule.swift):
import UIKit
@objc(MyNativeModule)
class MyNativeModule: NSObject {
@objc
func getDeviceName(_ resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
resolve(UIDevice.current.name)
}
}
JS (index.js):
import { NativeModules } from 'react-native';
export default NativeModules.MyNativeModule;
Why it works: The prompt specifies the method and return type, so the AI provides the boilerplate.
12. Optimizing FlatList Performance
FlatList can be a bottleneck. Ask for optimization techniques.
Prompt:
I have a FlatList with 10,000 items. How can I optimize it? Provide code examples for `getItemLayout`, `memoization`, and `maxToRenderPerBatch`.
Example Output:
<FlatList
data={data}
renderItem={MemoizedItem}
getItemLayout={(_, index) => ({ length: 80, offset: 80 * index, index })}
maxToRenderPerBatch={10}
initialNumToRender={20}
windowSize={5}
/>
Why it works: These props reduce the number of renders and help with scroll performance.
13. Handling Safe Areas and Notches
Use SafeAreaView from react-native-safe-area-context.
Prompt:
Show how to use `SafeAreaProvider` and `SafeAreaView` from `react-native-safe-area-context` in a React Native app. Include a complete example.
Example Output:
import React from 'react';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import { Text, View } from 'react-native';
export default function App() {
return (
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1 }}>
<Text>This is inside safe area</Text>
</SafeAreaView>
</SafeAreaProvider>
);
}
Why it works: It ensures your UI respects notches and status bars.
14. Deep Linking and Universal Links
Set up deep linking with React Navigation.
Prompt:
Configure deep linking in React Navigation v6. Map the route `profile/:userId` to the Profile screen. Show the linking config and how to parse the userId.
Example Output:
import { Linking } from 'react-native';
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: 'home',
Profile: 'profile/:userId',
Settings: 'settings',
},
},
};
// In NavigationContainer, add linking={linking}
Why it works: This is a standard configuration. The AI can also generate the code to handle the param.
15. Offline Support with AsyncStorage
Caching data locally.
Prompt:
Create a utility to cache and retrieve JSON data with AsyncStorage. Include functions `setCachedData(key, data)` and `getCachedData(key)`. Use TypeScript.
Example Output:
import AsyncStorage from '@react-native-async-storage/async-storage';
export async function setCachedData<T>(key: string, data: T): Promise<void> {
try {
await AsyncStorage.setItem(key, JSON.stringify(data));
} catch (e) {
console.error('Failed to cache data', e);
}
}
export async function getCachedData<T>(key: string): Promise<T | null> {
try {
const raw = await AsyncStorage.getItem(key);
return raw ? JSON.parse(raw) as T : null;
} catch (e) {
console.error('Failed to read cache', e);
return null;
}
}
Why it works: This is a simple wrapper, but you can extend it with TTL (time-to-live) for invalidation.
16. Error Boundary with Logging
Handle JS errors gracefully.
Prompt:
Create an ErrorBoundary class component in React Native that catches errors, logs them to console, and shows a fallback UI. Use TypeScript.
Example Output:
import React from 'react';
import { View, Text } from 'react-native';
interface ErrorBoundaryProps { children: React.ReactNode; }
interface ErrorBoundaryState { hasError: boolean; }
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('ErrorBoundary caught an error', error, info);
}
render() {
if (this.state.hasError) {
return <View><Text>Something went wrong.</Text></View>;
}
return this.props.children;
}
}
Why it works: It's a standard pattern, and the AI can integrate logging services like Sentry.
17. Biometric Authentication
Use react-native-biometrics or expo-local-authentication.
Prompt:
Show how to implement fingerprint authentication in React Native using `react-native-biometrics`. Provide a function that returns a boolean indicating success.
Example Output:
import ReactNativeBiometrics from 'react-native-biometrics';
export async function authenticateWithBiometrics(): Promise<boolean> {
const { available } = await ReactNativeBiometrics.isSensorAvailable();
if (!available) return false;
const { success } = await ReactNativeBiometrics.simplePrompt({ promptMessage: 'Confirm fingerprint' });
return success;
}
Why it works: It's a straightforward API, but the AI can add error handling.
18. Code Splitting and Lazy Loading
React Native supports lazy loading via React.lazy? Not directly, but you can use Suspense with dynamic imports for modules.
Prompt:
How can I implement lazy loading for a heavy screen in React Native? Show an example using React.lazy and Suspense.
Example Output:
import React, { Suspense, lazy } from 'react';
import { View, ActivityIndicator } from 'react-native';
const HeavyScreen = lazy(() => import('./HeavyScreen'));
export const LazyScreenWrapper = () => (
<Suspense fallback={<View style={{ flex: 1, justifyContent: 'center' }}><ActivityIndicator /></View>}>
<HeavyScreen />
</Suspense>
);
Why it works: It's a pattern that can reduce initial bundle size, though it's more common in web. For React Native, consider using react-native-bundle-splitter or "unbundled" mode.
Final Thoughts
These prompts are starting points. The key is to be specific about the requirements, provide context about your project (TypeScript, version, libraries), and iterate. AI is not a replacement for understanding the underlying concepts—it's a tool to accelerate your workflow. Start by copying these prompts, adapt them to your needs, and soon you'll be coding with an AI copilot that feels like a senior dev on call.
Happy coding! If you have your own favorite prompt patterns, share them in the comments below.
Comments