| name | mobile-development |
| description | Cross-platform mobile development with React Native and Expo including navigation, state management, and native features |
| category | mobile |
| triggers | ["mobile development","react native","expo","cross platform","mobile app","ios android","native features"] |
Mobile Development
Build cross-platform mobile applications with React Native and Expo. This skill covers component architecture, navigation patterns, state management, and native feature integration.
Purpose
Create production-ready mobile applications:
- Build iOS and Android apps from single codebase
- Implement native navigation patterns
- Manage application state effectively
- Access device features (camera, location, notifications)
- Handle offline-first architecture
- Optimize performance for mobile devices
Features
1. Expo Project Setup
npx create-expo-app@latest my-app --template tabs
my-app/
├── app/
│ ├── (tabs)/
│ │ ├── _layout.tsx
│ │ ├── index.tsx
│ │ └── profile.tsx
│ ├── _layout.tsx
│ └── modal.tsx
├── components/
├── hooks/
├── services/
├── store/
├── constants/
└── assets/
import { Stack } from 'expo-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
const queryClient = new QueryClient();
export default function RootLayout() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<QueryClientProvider client={queryClient}>
<AuthProvider>
<ThemeProvider>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
);
}
2. Navigation Patterns
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
}}
>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <Ionicons name="home" size={24} color={color} />,
}}
/>
<Tabs.Screen
name="explore"
options={{
title: 'Explore',
({ }) => ,
}}
/>
,
}}
/>
);
}
{ , } ;
{ useAuth } ;
() {
{ isAuthenticated, isLoading } = ();
(isLoading) {
;
}
(!isAuthenticated) {
;
}
(
);
}
{
: {
: ,
: {
:
},
: [
[
,
{
:
}
]
]
}
}
3. State Management with Zustand
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface User {
id: string;
name: string;
email: string;
avatar?: string;
}
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (user: User, token: string) => void;
logout: () => void;
updateUser: (updates: Partial<User>) => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
: ,
: ,
: ,
: ({
user,
token,
: ,
}),
: ({
: ,
: ,
: ,
}),
: ( ({
: state. ? { ...state., ...updates } : ,
})),
}),
{
: ,
: ( ),
}
)
);
{
: ;
: ;
: ;
: ;
: ;
}
{
: [];
: ;
: ;
: ;
: ;
: ;
: ;
}
useCartStore = create<>( ({
: [],
: ( {
existing = state..( i. === item.);
(existing) {
{
: state..(
i. === item.
? { ...i, : i. + item. }
: i
),
};
}
{
: [...state., { ...item, : () }],
};
}),
: ( ({
: state..( i. !== id),
})),
: ( ({
: quantity >
? state..( i. === id ? { ...i, quantity } : i)
: state..( i. !== id),
})),
: ({ : [] }),
: ()..( sum + item. * item., ),
: ()..( count + item., ),
}));
4. API Integration with React Query
import axios from 'axios';
import { useAuthStore } from '@/store/useStore';
const api = axios.create({
baseURL: process.env.EXPO_PUBLIC_API_URL,
});
api.interceptors.request.use((config) => {
const token = useAuthStore.getState().token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
useAuthStore.getState().logout();
}
return Promise.reject(error);
}
);
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function () {
({
: [, categoryId],
: () => {
params = categoryId ? { : categoryId } : {};
{ data } = api.(, { params });
data;
},
: * * ,
});
}
() {
({
: [, id],
: () => {
{ data } = api.();
data;
},
});
}
() {
queryClient = ();
({
: (: ) => {
{ data } = api.(, orderData);
data;
},
: {
queryClient.({ : [] });
},
});
}
() {
({
: [, ],
: ({ pageParam = }) => {
{ data } = api.(, {
: { : pageParam, : },
});
data;
},
:
lastPage. ? lastPage. + : ,
});
}
5. Native Features
import { Camera, CameraType } from 'expo-camera';
import * as ImagePicker from 'expo-image-picker';
export function CameraScreen() {
const [permission, requestPermission] = Camera.useCameraPermissions();
const [type, setType] = useState(CameraType.back);
const cameraRef = useRef<Camera>(null);
async function takePicture() {
if (cameraRef.current) {
const photo = await cameraRef.current.takePictureAsync({
quality: 0.8,
base64: false,
});
await uploadImage(photo.uri);
}
}
async function pickImage() {
const result = await ImagePicker.({
: ..,
: ,
: [, ],
: ,
});
(!result.) {
(result.[].);
}
}
(!permission?.) {
(
);
}
(
);
}
* ;
() {
[location, setLocation] = useState<. | >();
[error, setError] = useState< | >();
( {
( () => {
{ status } = .();
(status !== ) {
();
;
}
currentLocation = .({
: ..,
});
(currentLocation);
})();
}, []);
{ location, error };
}
* ;
* ;
.({
: () => ({
: ,
: ,
: ,
}),
});
(): < | > {
(!.) {
.();
;
}
{ : existingStatus } = .();
finalStatus = existingStatus;
(existingStatus !== ) {
{ status } = .();
finalStatus = status;
}
(finalStatus !== ) {
;
}
token = .({
: process..,
});
token.;
}
() {
[expoPushToken, setExpoPushToken] = useState< | >();
notificationListener = useRef<.>();
responseListener = useRef<.>();
( {
().(setExpoPushToken);
notificationListener. = .(
{
.(, notification);
}
);
responseListener. = .(
{
data = response....;
(data.) {
router.(data.);
}
}
);
{
notificationListener.?.();
responseListener.?.();
};
}, []);
{ expoPushToken };
}
6. Performance Optimization
import { FlashList } from '@shopify/flash-list';
interface ProductListProps {
products: Product[];
onEndReached: () => void;
}
export function ProductList({ products, onEndReached }: ProductListProps) {
const renderItem = useCallback(({ item }: { item: Product }) => (
<ProductCard product={item} />
), []);
const keyExtractor = useCallback((item: Product) => item.id, []);
return (
<FlashList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
estimatedItemSize={200}
onEndReached={onEndReached}
onEndReachedThreshold={0.5}
ItemSeparatorComponent={Separator}
ListEmptyComponent={EmptyState}
/>
);
}
= ( () {
navigation = ();
handlePress = ( {
navigation.(, { : product. });
}, [product., navigation]);
(
);
});
{ } ;
blurhash = ;
() {
(
);
}
{ } ;
() {
(
);
}
Use Cases
1. E-commerce App
export function ProductScreen() {
const { id } = useLocalSearchParams();
const { data: product, isLoading } = useProduct(id as string);
const addToCart = useCartStore((state) => state.addItem);
if (isLoading) return <ProductSkeleton />;
if (!product) return <NotFound />;
return (
<ScrollView>
<ImageGallery images={product.images} />
<View style={styles.content}>
<Text style={styles.name}>{product.name}</Text>
<Text style={styles.price}>${product.price}</Text>
<VariantSelector
variants={product.variants}
onSelect={setSelectedVariant}
/>
addToCart({
productId: product.id,
name: product.name,
price: product.price,
quantity: 1,
})}
/>
{product.description}
);
}
2. Social App with Real-time
import { io } from 'socket.io-client';
export function useChatRoom(roomId: string) {
const [messages, setMessages] = useState<Message[]>([]);
const socketRef = useRef<Socket>();
useEffect(() => {
const token = useAuthStore.getState().token;
socketRef.current = io(process.env.EXPO_PUBLIC_WS_URL!, {
auth: { token },
});
socketRef.current.emit('join', roomId);
socketRef.current.on('message', (message: Message) => {
setMessages((prev) => [...prev, message]);
});
return () => {
socketRef.current?.emit('leave', roomId);
socketRef.current?.disconnect();
};
}, [roomId]);
const sendMessage = useCallback((content: string) => {
socketRef.?.(, { roomId, content });
}, [roomId]);
{ messages, sendMessage };
}
Best Practices
Do's
- Use Expo for faster development - Managed workflow handles complexity
- Implement offline-first - Use AsyncStorage and optimistic updates
- Optimize images - Use expo-image with caching
- Use FlashList - Better performance than FlatList
- Test on real devices - Simulators don't show real performance
- Handle all permission states - Request gracefully
Don'ts
- Don't block the JS thread with heavy computations
- Don't use inline styles in render methods
- Don't forget to handle keyboard avoiding
- Don't ignore deep linking setup
- Don't skip splash screen configuration
- Don't neglect accessibility
Performance Checklist
## Mobile Performance Checklist
### Rendering
- [ ] Use memo for expensive components
- [ ] Implement proper list virtualization
- [ ] Optimize images (size, format, caching)
- [ ] Avoid inline function props
### State
- [ ] Split stores by domain
- [ ] Use selectors for derived state
- [ ] Persist critical data
- [ ] Handle loading/error states
### Network
- [ ] Implement request caching
- [ ] Use optimistic updates
- [ ] Handle offline gracefully
- [ ] Implement retry logic
Related Skills
- react - React fundamentals
- typescript - Type-safe development
- frontend-design - UI/UX patterns
- api-architecture - Backend integration
Reference Resources