expo-modules
Use when working with Expo SDK modules for camera, location, notifications, file system, and other device APIs. Covers permissions, configurations, and best practices.
What this skill does
# Expo Modules
Use this skill when working with Expo's extensive SDK modules for accessing device features and native functionality.
## Key Concepts
### Camera
```tsx
import { Camera, CameraType } from 'expo-camera';
import { useState } from 'react';
import { Button, View } from 'react-native';
export default function CameraScreen() {
const [permission, requestPermission] = Camera.useCameraPermissions();
const [type, setType] = useState(CameraType.back);
if (!permission?.granted) {
return (
<View>
<Button title="Grant Permission" onPress={requestPermission} />
</View>
);
}
return (
<Camera style={{ flex: 1 }} type={type}>
<Button
title="Flip Camera"
onPress={() =>
setType(type === CameraType.back ? CameraType.front : CameraType.back)
}
/>
</Camera>
);
}
```
### Location
```tsx
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';
export function useLocation() {
const [location, setLocation] = useState<Location.LocationObject | null>(null);
useEffect(() => {
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return;
const loc = await Location.getCurrentPositionAsync({});
setLocation(loc);
})();
}, []);
return location;
}
```
### Notifications
```tsx
import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
export function useNotifications() {
useEffect(() => {
const subscription = Notifications.addNotificationReceivedListener(
(notification) => {
console.log(notification);
}
);
return () => subscription.remove();
}, []);
const sendNotification = async () => {
await Notifications.scheduleNotificationAsync({
content: {
title: 'Hello!',
body: 'This is a notification',
},
trigger: { seconds: 2 },
});
};
return { sendNotification };
}
```
### File System
```tsx
import * as FileSystem from 'expo-file-system';
export async function saveFile(data: string, filename: string) {
const uri = `${FileSystem.documentDirectory}${filename}`;
await FileSystem.writeAsStringAsync(uri, data);
return uri;
}
export async function readFile(filename: string) {
const uri = `${FileSystem.documentDirectory}${filename}`;
const content = await FileSystem.readAsStringAsync(uri);
return content;
}
export async function downloadFile(url: string, filename: string) {
const uri = `${FileSystem.documentDirectory}${filename}`;
const download = await FileSystem.downloadAsync(url, uri);
return download.uri;
}
```
### Image Picker
```tsx
import * as ImagePicker from 'expo-image-picker';
import { useState } from 'react';
import { Button, Image, View } from 'react-native';
export default function ImagePickerScreen() {
const [image, setImage] = useState<string | null>(null);
const pickImage = async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.canceled) {
setImage(result.assets[0].uri);
}
};
return (
<View>
<Button title="Pick Image" onPress={pickImage} />
{image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
</View>
);
}
```
## Best Practices
### Permission Handling
```tsx
import * as Location from 'expo-location';
async function requestLocationPermission() {
const { status: foregroundStatus } =
await Location.requestForegroundPermissionsAsync();
if (foregroundStatus !== 'granted') {
console.log('Permission denied');
return false;
}
// Request background permission only if needed
const { status: backgroundStatus } =
await Location.requestBackgroundPermissionsAsync();
return backgroundStatus === 'granted';
}
```
### Secure Storage
```tsx
import * as SecureStore from 'expo-secure-store';
export async function saveToken(key: string, value: string) {
await SecureStore.setItemAsync(key, value);
}
export async function getToken(key: string) {
return await SecureStore.getItemAsync(key);
}
export async function deleteToken(key: string) {
await SecureStore.deleteItemAsync(key);
}
```
### Device Info
```tsx
import * as Device from 'expo-device';
import * as Application from 'expo-application';
import Constants from 'expo-constants';
export function getDeviceInfo() {
return {
deviceName: Device.deviceName,
deviceType: Device.deviceType,
osName: Device.osName,
osVersion: Device.osVersion,
appVersion: Application.nativeApplicationVersion,
buildVersion: Application.nativeBuildVersion,
expoVersion: Constants.expoVersion,
};
}
```
## Common Patterns
### Persistent Storage
```tsx
import AsyncStorage from '@react-native-async-storage/async-storage';
export const storage = {
async set(key: string, value: any) {
await AsyncStorage.setItem(key, JSON.stringify(value));
},
async get<T>(key: string): Promise<T | null> {
const item = await AsyncStorage.getItem(key);
return item ? JSON.parse(item) : null;
},
async remove(key: string) {
await AsyncStorage.removeItem(key);
},
async clear() {
await AsyncStorage.clear();
},
};
```
### Background Tasks
```tsx
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const BACKGROUND_FETCH_TASK = 'background-fetch';
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
// Do work here
console.log('Background task running');
return BackgroundFetch.BackgroundFetchResult.NewData;
});
export async function registerBackgroundTask() {
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 60 * 15, // 15 minutes
stopOnTerminate: false,
startOnBoot: true,
});
}
```
### Sharing Content
```tsx
import * as Sharing from 'expo-sharing';
export async function shareContent(uri: string) {
const isAvailable = await Sharing.isAvailableAsync();
if (!isAvailable) {
console.log('Sharing is not available');
return;
}
await Sharing.shareAsync(uri, {
mimeType: 'image/jpeg',
dialogTitle: 'Share this image',
});
}
```
## Related Skills
- **expo-config**: Configuring module permissions
- **expo-build**: Including modules in builds
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.