React Native Quick Start Guide
Initial Setup
1. Choose Framework: Expo (Recommended)
Expo is recommended because:
- Easier setup and development
- Built-in OTA updates
- Managed workflow (less native code)
- Great developer experience
2. Install Expo CLI
npm install -g expo-cli
# or
npm install -g @expo/cli
3. Create New App in Monorepo
cd apps
npx create-expo-app mobile --template blank-typescript
4. Update Root package.json
Add mobile scripts:
{
"scripts": {
"dev:mobile": "cd apps/mobile && npm start",
"dev:mobile:ios": "cd apps/mobile && npm run ios",
"dev:mobile:android": "cd apps/mobile && npm run android"
}
}
5. Install Core Dependencies
cd apps/mobile
npm install @supabase/supabase-js @react-native-async-storage/async-storage
npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs
npm install react-native-screens react-native-safe-area-context
npm install react-hook-form zod
npm install date-fns
npm install @tanstack/react-query
6. Install Development Dependencies
npm install --save-dev @types/react @types/react-native
Project Structure
apps/mobile/
├── src/
│ ├── screens/
│ │ ├── auth/
│ │ │ ├── SignInScreen.tsx
│ │ │ └── SignUpScreen.tsx
│ │ ├── dashboard/
│ │ │ ├── DashboardScreen.tsx
│ │ │ ├── EntryDetailScreen.tsx
│ │ │ └── EntryEditorScreen.tsx
│ │ └── settings/
│ │ └── SettingsScreen.tsx
│ ├── components/
│ │ ├── ui/
│ │ │ ├── Button.tsx
│ │ │ ├── Card.tsx
│ │ │ ├── Input.tsx
│ │ │ └── Textarea.tsx
│ │ └── EntryCard.tsx
│ ├── navigation/
│ │ ├── AppNavigator.tsx
│ │ ├── AuthNavigator.tsx
│ │ └── MainNavigator.tsx
│ ├── services/
│ │ ├── supabase.ts
│ │ └── api.ts
│ ├── hooks/
│ │ ├── useAuth.ts
│ │ └── useEntries.ts
│ ├── contexts/
│ │ └── AuthContext.tsx
│ ├── utils/
│ │ ├── dateUtils.ts
│ │ └── fonts.ts
│ └── types/
│ └── index.ts
├── app.json
├── package.json
└── tsconfig.json
Basic Implementation Examples
1. Supabase Client Setup
// src/services/supabase.ts
import { createClient } from '@supabase/supabase-js'
import AsyncStorage from '@react-native-async-storage/async-storage'
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!
export const supabase = createClient(supabaseUrl, supabaseKey, {
auth: {
storage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: false,
},
})
2. Auth Context
// src/contexts/AuthContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react'
import { Session, User } from '@supabase/supabase-js'
import { supabase } from '../services/supabase'
interface AuthContextType {
user: User | null
session: Session | null
loading: boolean
signOut: () => Promise<void>
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [session, setSession] = useState<Session | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
setUser(session?.user ?? null)
setLoading(false)
})
// Listen for auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session)
setUser(session?.user ?? null)
}
)
return () => subscription.unsubscribe()
}, [])
const signOut = async () => {
await supabase.auth.signOut()
}
return (
<AuthContext.Provider value={{ user, session, loading, signOut }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}
3. Navigation Setup
// src/navigation/AppNavigator.tsx
import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { useAuth } from '../contexts/AuthContext'
import AuthNavigator from './AuthNavigator'
import MainNavigator from './MainNavigator'
const Stack = createNativeStackNavigator()
export default function AppNavigator() {
const { user, loading } = useAuth()
if (loading) {
return <LoadingScreen />
}
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{user ? (
<Stack.Screen name="Main" component={MainNavigator} />
) : (
<Stack.Screen name="Auth" component={AuthNavigator} />
)}
</Stack.Navigator>
</NavigationContainer>
)
}
4. Dashboard Screen Example
// src/screens/dashboard/DashboardScreen.tsx
import React, { useState, useEffect } from 'react'
import { View, FlatList, StyleSheet } from 'react-native'
import { useQuery } from '@tanstack/react-query'
import { apiRequest } from '../../services/api'
import EntryCard from '../../components/EntryCard'
import { JournalEntry } from '../../types'
export default function DashboardScreen() {
const [page, setPage] = useState(0)
const pageSize = 20
const { data, isLoading, refetch } = useQuery({
queryKey: ['entries', page],
queryFn: async () => {
const response = await apiRequest<{ entries: JournalEntry[] }>(
`/api/journal/entries?page=${page}&limit=${pageSize}`
)
return response.entries
},
})
const loadMore = () => {
if (!isLoading && data && data.length === pageSize) {
setPage((p) => p + 1)
}
}
return (
<View style={styles.container}>
<FlatList
data={data || []}
renderItem={({ item }) => <EntryCard entry={item} />}
keyExtractor={(item) => item.id}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
refreshing={false}
onRefresh={refetch}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
})
Environment Setup
1. Create .env file
# apps/mobile/.env
EXPO_PUBLIC_SUPABASE_URL=your_supabase_url
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
EXPO_PUBLIC_API_URL=https://your-backend.railway.app
2. Install dotenv
npm install dotenv
3. Update app.json
{
"expo": {
"name": "HiveJournal",
"slug": "hivejournal",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.hivejournal.app"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.hivejournal.app"
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
Running the App
Development
# Start Expo dev server
npm run dev:mobile
# Run on iOS simulator
npm run dev:mobile:ios
# Run on Android emulator
npm run dev:mobile:android
Building
# Install EAS CLI
npm install -g eas-cli
# Login to Expo
eas login
# Configure EAS
eas build:configure
# Build for iOS
eas build --platform ios
# Build for Android
eas build --platform android
Next Steps
- Set up the project structure
- Implement authentication
- Build dashboard screen
- Add entry detail and editor
- Implement remaining features
See REACT_NATIVE_PLAN.md for detailed feature breakdown and roadmap.