Skip to content
Tutorial··4 min read

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.

By

Build a React Native app with Claude Code — from zero to TestFlight

Mobile boilerplate is repetitive, which makes it a good fit for Claude Code. 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)

Prerequisites

  • Node.js
  • macOS and Xcode (for the iOS Simulator)
  • Android Studio (for Android emulator, optional)
  • Expo account (free at expo.dev)
  • A paid Apple Developer account (required to submit to App Store Connect and TestFlight)
  • Supabase account (free tier)
  • Claude Code installed

Step 1 — Create the Expo project

bash
npx create-expo-app HabitTracker --template blank-typescript
cd HabitTracker

Before starting Claude Code, install the React Native skill into the project:

bash
curl -fsSL --create-dirs -o .claude/skills/react-native-mobile/SKILL.md https://claudeskil.com/skills/react-native-mobile/SKILL.md

This gives Claude Code context about Expo, React Navigation, and the Expo SDK, loaded whenever a request matches — saves you from explaining these patterns every session. Then start Claude Code:

bash
claude

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 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 with npx expo install --fix, so every package matches our Expo SDK version.

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 — use one or the other, not both.

Step 4 — Supabase integration

Install:

bash
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 detail: Supabase needs a custom storage adapter for React Native since localStorage doesn't exist. Verify the supabase.ts file uses AsyncStorage:

ts
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.

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: you can test push notifications on a physical device, on an Android Emulator with Google Play services, or on an iOS Simulator running on Xcode 14 or later.

Step 7 — Build for TestFlight

First, install the EAS CLI, log in, and configure the project:

bash
npm install --global eas-cli
eas login
eas build:configure

TestFlight needs a store build, so build with the production profile, then submit it:

bash
eas build --platform ios --profile production
eas submit --platform ios

The eas submit step uploads the build to App Store Connect for TestFlight (you need the paid Apple Developer account for this step). eas build --platform ios --auto-submit combines both commands.

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

  • 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.