Part 6: Quick Reference Cards
Print these. Keep them handy. Ship faster.
EAS Build Commands
# Install EAS CLI
npm install -g eas-cli
# Login
eas login
# Configure project (first time)
eas build:configure
# Development build (with dev client)
eas build --platform ios --profile development
eas build --platform android --profile development
# Preview build (for testing)
eas build --platform ios --profile preview
eas build --platform android --profile preview
# Production build
eas build --platform ios --profile production
eas build --platform android --profile production
# Build both platforms
eas build --platform all --profile production
# Check build status
eas build:list
# Submit to stores
eas submit --platform ios
eas submit --platform androidExpo Router Cheat Sheet
File-Based Routing
app/
├── _layout.tsx → Root layout
├── index.tsx → "/" (home)
├── about.tsx → "/about"
├── [id].tsx → "/123" (dynamic)
├── [...rest].tsx → "/a/b/c" (catch-all)
├── (auth)/ → Group (no URL segment)
│ ├── login.tsx → "/login"
│ └── signup.tsx → "/signup"
└── (tabs)/ → Tab navigator
├── _layout.tsx → Tab layout
├── index.tsx → "/tabs"
└── profile.tsx → "/tabs/profile"Navigation
import { router, Link, useLocalSearchParams } from 'expo-router';
// Imperative navigation
router.push('/profile'); // Push to stack
router.replace('/home'); // Replace current
router.back(); // Go back
router.navigate('/settings'); // Smart navigation
// With parameters
router.push({
pathname: '/user/[id]',
params: { id: '123' },
});
// Link component
<Link href="/about">About</Link>
<Link href={{ pathname: '/user/[id]', params: { id: '123' } }}>User</Link>
// Get parameters
const { id } = useLocalSearchParams<{ id: string }>();Screenshot Sizes
iOS (Required)
| Device | Size (pixels) | Required |
|---|---|---|
| iPhone 6.7" | 1290 x 2796 | Yes |
| iPhone 6.5" | 1284 x 2778 | Yes |
| iPhone 5.5" | 1242 x 2208 | If targeting |
| iPad Pro 12.9" | 2048 x 2732 | If iPad app |
| iPad Pro 11" | 1668 x 2388 | If iPad app |
Android (Required)
| Type | Size (pixels) | Notes |
|---|---|---|
| Phone | 1080 x 1920 min | Up to 3840 x 2160 |
| Feature Graphic | 1024 x 500 | Required |
| Hi-res Icon | 512 x 512 | Required |
App Icon Sizes
Required Export
| Size | Use |
|---|---|
| 1024 x 1024 | App Store / Play Store |
iOS Auto-Generated From 1024
| Size | Use |
|---|---|
| 180 x 180 | iPhone (3x) |
| 120 x 120 | iPhone (2x) |
| 167 x 167 | iPad Pro |
| 152 x 152 | iPad |
| 40 x 40 | Spotlight |
| 29 x 29 | Settings |
Convex Quick Reference
Client Setup
import { ConvexReactClient } from 'convex/react';
import AsyncStorage from '@react-native-async-storage/async-storage';
const convex = createClient(
process.env.CONVEX_URL!,
process.env.CONVEX_AUTH_TOKEN!,
{
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
}
);Auth Operations
// Sign up
await convex.auth.signUp({ email, password });
// Sign in
await convex.auth.signInWithPassword({ email, password });
// Sign out
await convex.auth.signOut();
// Get current user
const { data: { user } } = await convex.auth.getUser();
// Get session
const { data: { session } } = await convex.auth.getSession();
// Listen to auth changes
convex.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') { /* */ }
if (event === 'SIGNED_OUT') { /* */ }
});Database Operations
// Select
const { data } = await convex
.from('posts')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false });
// Insert
const { data } = await convex
.from('posts')
.insert({ title: 'Hello', user_id: userId })
.select()
.single();
// Update
const { data } = await convex
.from('posts')
.update({ title: 'Updated' })
.eq('id', postId)
.select()
.single();
// Delete
await convex
.from('posts')
.delete()
.eq('id', postId);Edge Functions
// Call edge function
const { data, error } = await convex.functions.invoke('my-function', {
body: { key: 'value' },
});RevenueCat Quick Reference
Setup
import Purchases from 'react-native-purchases';
import { Platform } from 'react-native';
await Purchases.configure({
apiKey: Platform.OS === 'ios'
? 'appl_xxxxx'
: 'goog_xxxxx',
appUserID: userId, // optional
});Common Operations
// Get offerings
const { current } = await Purchases.getOfferings();
const packages = current?.availablePackages;
// Purchase
const { customerInfo } = await Purchases.purchasePackage(package);
// Check entitlement
const { customerInfo } = await Purchases.getCustomerInfo();
const isPro = customerInfo.entitlements.active['pro'] !== undefined;
// Restore purchases
const { customerInfo } = await Purchases.restorePurchases();
// Set user ID (after auth)
await Purchases.logIn(userId);
// Logout (reset to anonymous)
await Purchases.logOut();Zustand Quick Reference
Create Store
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface BearStore {
bears: number;
increase: () => void;
reset: () => void;
}
const useBearStore = create<BearStore>()(
persist(
(set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
reset: () => set({ bears: 0 }),
}),
{
name: 'bear-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);Use Store
// In component
const bears = useBearStore((state) => state.bears);
const increase = useBearStore((state) => state.increase);
// Or get all
const { bears, increase, reset } = useBearStore();
// Outside React
const bears = useBearStore.getState().bears;
useBearStore.setState({ bears: 10 });Environment Variables
Setup (.env)
# .env (never commit)
CONVEX_URL=https://xxx.convex.co
CONVEX_AUTH_TOKEN=eyJhbGc...
EXPO_PUBLIC_REVENUECAT_IOS=appl_xxx
EXPO_PUBLIC_REVENUECAT_ANDROID=goog_xxx
EXPO_PUBLIC_POSTHOG_KEY=phc_xxxUsage
// In code
const convexUrl = process.env.CONVEX_URL!;
// Note: Only EXPO_PUBLIC_ prefixed vars are available in clientEAS Secrets
# Set for EAS builds
eas secret:create --name SENTRY_AUTH_TOKEN --value xxx
# List secrets
eas secret:listVersion Numbering
app.json
{
"expo": {
"version": "1.0.0",
"ios": {
"buildNumber": "1"
},
"android": {
"versionCode": 1
}
}
}Rules
| Field | When to Change |
|---|---|
| version | User-facing updates (1.0.0 → 1.1.0) |
| buildNumber | Every iOS build (must always increase) |
| versionCode | Every Android build (must always increase) |
Semantic Versioning
MAJOR.MINOR.PATCH
│ │ └── Bug fixes (1.0.1)
│ └──────── New features (1.1.0)
└────────────── Breaking changes (2.0.0)Common TypeScript Patterns
Component Props
interface ButtonProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ title, onPress, variant = 'primary', disabled }: ButtonProps) {
// ...
}API Response
interface ApiResponse<T> {
data: T | null;
error: string | null;
loading: boolean;
}
// Usage
const [state, setState] = useState<ApiResponse<User>>({
data: null,
error: null,
loading: true,
});Async Handler
const handleSubmit = async () => {
try {
setLoading(true);
const result = await api.submit(data);
setSuccess(true);
} catch (error) {
setError(error instanceof Error ? error.message : 'Unknown error');
} finally {
setLoading(false);
}
};Useful Links
Official Documentation
- Expo Docs - docs.expo.dev
- React Native Docs - reactnative.dev
- Convex Docs - convex.com/docs
- RevenueCat Docs - revenuecat.com/docs
App Store Guidelines
- Apple App Store Review - developer.apple.com/app-store/review/guidelines/
- Google Play Policy - play.google.com/about/developer-content-policy/
Tools
- EAS Build - expo.dev/eas
- Coolors - Color palettes
- Canva - Screenshots
- Remove.bg - Background removal
The Final Checklist (Before You Submit)
- App works on real device
- Apple Sign-In implemented (if any auth)
- No API keys in client code
- RLS enabled on all tables
- Privacy policy link works
- All screenshots uploaded
- App icon looks good at small sizes
- Version/build numbers incremented
- Production environment configured
- Analytics tracking key events
- Error tracking enabled
You're ready to ship. Go do it.
No More Lookups
Everything configured correctly
All the sizes, commands, and configurations already in place.
Proper dimensions
Build scripts ready
ESLint configured
Get ShipReactNative
Save 40+ hours of setup