Tutorial··14 min
Build a React Native app with Claude Code — from zero to TestFlight
A real walkthrough: use Claude Code to build, test, and deploy a React Native app with Expo, navigation, and a Supabase backend. Covers the mobile-specific gotchas that trip up web developers.
Building a React Native app with Claude Code is genuinely fast — faster than any web project, because mobile boilerplate is repetitive and the agent handles it well. But mobile development has its own set of gotchas: native dependencies, simulator quirks, code signing, and the App Store review process. This walkthrough covers all of it.
What we're building
--------------------
A habit tracker app:
- Home screen: today's habits with checkboxes
- Progress screen: streak and completion chart
- Settings: add/remove habits, set reminders
- Expo push notifications
- Supabase backend for persistence and auth
- Deployed to TestFlight (iOS) and internal testing (Android)
Prerequisites
--------------
- macOS (required for iOS builds), Node.js 18+
- Xcode 15+ (for iOS simulator and TestFlight)
- Android Studio (for Android emulator, optional)
- Expo account (free at expo.dev)
- Supabase account (free tier)
- Claude Code installed
Step 1 — Create the Expo project
----------------------------------
npx create-expo-app HabitTracker --template blank-typescript
cd HabitTracker
claude
Install the React Native skill:
claude skills add react-native-mobile
This loads context about Expo, React Navigation, and the Expo SDK — saves you from explaining these patterns every session.
Step 2 — Set up navigation
---------------------------
> install @react-navigation/native, @react-navigation/bottom-tabs, and the required Expo dependencies (expo-linking, expo-constants, react-native-screens, react-native-safe-area-context). Create a bottom tab navigator with three tabs: Today (HomeScreen), Progress (ProgressScreen), and Settings (SettingsScreen). Create placeholder screen components for each. Handle the safe area properly on iOS.
A critical detail Claude Code sometimes gets wrong on Expo: the Expo dependency versions must align with the SDK version. If you see peer dependency warnings, ask:
> fix any peer dependency version conflicts. we're using Expo SDK 51.
Step 3 — Today screen with habits
-----------------------------------
> create the TodayScreen. It should: 1) fetch today's habits from a local array (we'll connect Supabase later), 2) render each habit as a row with a checkbox, the habit name, and the current streak number, 3) tapping the checkbox marks the habit complete for today, updating local state, 4) completed habits move to a "Done" section below. use StyleSheet for styling, not inline styles.
Review the component carefully. Two common issues: the checkbox state not persisting on re-render (needs useState or useReducer, not a plain variable) and the "Done" section creating a FlatList nested inside a ScrollView (causes crashes — use one or the other, not both).
Step 4 — Supabase integration
------------------------------
Install:
npx expo install @supabase/supabase-js
> create lib/supabase.ts — a Supabase client configured for React Native. Use AsyncStorage from @react-native-async-storage/async-storage for session persistence instead of localStorage. Create tables: habits (id, user_id, name, color, created_at) and completions (id, habit_id, user_id, completed_date). Add RLS policies so users only see their own data.
One React Native–specific thing the agent sometimes misses: Supabase needs a custom storage adapter for React Native since localStorage doesn't exist. Verify the supabase.ts file uses AsyncStorage:
import AsyncStorage from '@react-native-async-storage/async-storage';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
auth: { storage: AsyncStorage, autoRefreshToken: true, persistSession: true }
});
If it used localStorage or omitted the storage option, ask it to fix that explicitly.
Step 5 — Auth (magic link)
---------------------------
> add a sign-in screen using Supabase magic link auth. The screen should have an email input and a "Send magic link" button. On success, show "Check your email" message. Handle the deep link callback — when the user taps the link in their email, the app should open and exchange the token for a session. Use expo-linking for the deep link handling. Add the app scheme to app.json.
Deep linking on Expo has a specific setup. Ask Claude Code to:
> update app.json to add scheme: "habittracker". Update the Supabase redirect URL in the auth screen to use Linking.createURL('auth/callback'). Create a useAuthDeepLink hook that listens for incoming URLs and calls supabase.auth.exchangeCodeForSession.
Step 6 — Push notifications
-----------------------------
> install expo-notifications. Create a lib/notifications.ts module that: 1) requests permission on first run, 2) gets the Expo push token, 3) stores it in the Supabase profiles table, 4) schedules a daily reminder at a user-configurable time. Add a notification time picker to the Settings screen.
Note: push notifications don't work in the iOS simulator. You need a physical device or Expo Go to test them. Ask Claude Code to add a dev-only mock mode:
> add a isDev check — in dev mode, log the notification payload to the console instead of scheduling it, so we can test the settings screen flow without a physical device.
Step 7 — Build for TestFlight
-------------------------------
First, configure your Expo app credentials:
npx eas login
npx eas build:configure
Then build:
npx eas build --platform ios --profile preview
The --profile preview flag builds for internal testing (TestFlight) without requiring a full store review. This is the fastest path to sharing with beta testers.
Once the build completes (~15 minutes), Expo will give you a link to download the .ipa. Upload it to App Store Connect:
xcrun altool --upload-app -f your-build.ipa -t ios -u your@apple.com -p your-app-specific-password
Ask Claude Code to write the TestFlight release notes:
> write TestFlight release notes for v0.1.0 of a habit tracker app with daily check-ins, streak tracking, and push reminders.
Mobile-specific things Claude Code handles well
-------------------------------------------------
- Expo SDK version compatibility (it knows which packages need which SDK version)
- Navigation patterns (when to use stack vs. tab vs. modal)
- Platform-specific conditionals (Platform.OS === 'ios' checks)
- StyleSheet.create() patterns vs. inline styles
- FlatList vs ScrollView vs SectionList trade-offs
Things to double-check yourself
---------------------------------
- Native module setup (any package requiring npx pod-install or manual Xcode config)
- Code signing configuration in eas.json
- App Store metadata (screenshots, privacy manifest, age rating)
- App Transport Security settings for custom API endpoints
Explore more React Native and Flutter skills at claudeskil.com/category/ios-developer.