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 android

Expo 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)

DeviceSize (pixels)Required
iPhone 6.7"1290 x 2796Yes
iPhone 6.5"1284 x 2778Yes
iPhone 5.5"1242 x 2208If targeting
iPad Pro 12.9"2048 x 2732If iPad app
iPad Pro 11"1668 x 2388If iPad app

Android (Required)

TypeSize (pixels)Notes
Phone1080 x 1920 minUp to 3840 x 2160
Feature Graphic1024 x 500Required
Hi-res Icon512 x 512Required

App Icon Sizes

Required Export

SizeUse
1024 x 1024App Store / Play Store

iOS Auto-Generated From 1024

SizeUse
180 x 180iPhone (3x)
120 x 120iPhone (2x)
167 x 167iPad Pro
152 x 152iPad
40 x 40Spotlight
29 x 29Settings

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_xxx

Usage

// In code
const convexUrl = process.env.CONVEX_URL!;

// Note: Only EXPO_PUBLIC_ prefixed vars are available in client

EAS Secrets

# Set for EAS builds
eas secret:create --name SENTRY_AUTH_TOKEN --value xxx

# List secrets
eas secret:list

Version Numbering

app.json

{
  "expo": {
    "version": "1.0.0",
    "ios": {
      "buildNumber": "1"
    },
    "android": {
      "versionCode": 1
    }
  }
}

Rules

FieldWhen to Change
versionUser-facing updates (1.0.0 → 1.1.0)
buildNumberEvery iOS build (must always increase)
versionCodeEvery 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);
  }
};

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