React Native Project Structure – Building for Scale
A well-organized React Native project is critical for team productivity, maintainability, and performance. In 2026, the industry standard is feature-based modular architecture combined with Clean Architecture principles. This guide presents production-tested structures for apps of all sizes — from small startups to large enterprise applications.
- High-Level Project Structure
The root of your React Native project should separate configuration, source code, assets, and tests. Below is the recommended structure for a modern React Native app using TypeScript.
MyReactNativeApp/ ├── .husky/ # Git hooks (pre-commit, pre-push) ├── .vscode/ # Editor configuration ├── __tests__/ # Root-level test setup ├── android/ # Native Android project ├── ios/ # Native iOS project ├── patches/ # Patch-package patches for dependencies ├── scripts/ # Build and deployment scripts ├── src/ # Main source code (see detailed breakdown) ├── .env.development # Dev environment variables ├── .env.production # Prod environment variables ├── .env.staging # Staging environment variables ├── .eslintrc.js # ESLint configuration ├── .prettierrc # Prettier configuration ├── .swiftlint.yml # Swift linting for iOS ├── app.config.js # Expo config (if using Expo) ├── babel.config.js # Babel configuration ├── detox.config.js # E2E testing (Detox) ├── jest.config.js # Unit testing configuration ├── metro.config.js # Metro bundler configuration ├── package.json # Dependencies and scripts ├── react-native.config.js # React Native configuration ├── tsconfig.json # TypeScript configuration └── yarn.lock / package-lock.json # Lock file
- Source Code Structure (src/)
The src/ folder contains all application code organized by features and layers. This structure scales from 1 to 100+ developers.
src/
├── app/ # App initialization & configuration
│ ├── App.tsx # Root component
│ ├── index.tsx # Entry point
│ ├── providers/ # Context providers
│ │ ├── ThemeProvider.tsx
│ │ ├── AuthProvider.tsx
│ │ ├── QueryProvider.tsx # React Query setup
│ │ └── index.tsx # Combined providers
│ └── navigation/ # Root navigation
│ ├── RootNavigator.tsx
│ ├── NavigationTypes.ts
│ └── linking.ts # Deep linking configuration
│
├── core/ # Core infrastructure (reusable across features)
│ ├── api/ # API client setup
│ │ ├── client.ts # Axios/Fetch client
│ │ ├── endpoints.ts # API endpoint constants
│ │ ├── interceptors.ts # Request/response interceptors
│ │ └── types.ts # API response types
│ ├── config/ # App configuration
│ │ ├── env.ts # Environment variables
│ │ ├── constants.ts # App constants
│ │ └── featureFlags.ts # Feature flag system
│ ├── hooks/ # Global reusable hooks
│ │ ├── useDebounce.ts
│ │ ├── useThrottle.ts
│ │ ├── useAppState.ts
│ │ └── useNetworkStatus.ts
│ ├── utils/ # Utility functions
│ │ ├── formatters.ts # Date, currency, number formatting
│ │ ├── validators.ts # Input validation
│ │ ├── storage.ts # AsyncStorage/mmkv wrapper
│ │ ├── analytics.ts # Analytics tracking
│ │ └── logger.ts # Logging utility
│ ├── types/ # Global TypeScript types
│ │ ├── global.d.ts
│ │ └── api.types.ts
│ └── constants/ # App-wide constants
│ ├── colors.ts
│ ├── typography.ts
│ └── spacing.ts
│
├── modules/ # Feature modules (grouped by domain)
│ ├── auth/ # Authentication feature
│ │ ├── components/ # Feature-specific components
│ │ │ ├── LoginForm.tsx
│ │ │ ├── SignUpForm.tsx
│ │ │ └── SocialLoginButton.tsx
│ │ ├── screens/ # Feature screens
│ │ │ ├── LoginScreen.tsx
│ │ │ ├── SignUpScreen.tsx
│ │ │ └── ForgotPasswordScreen.tsx
│ │ ├── hooks/ # Feature hooks
│ │ │ └── useAuth.ts
│ │ ├── services/ # Feature services (API calls)
│ │ │ └── authService.ts
│ │ ├── store/ # Feature state management
│ │ │ └── authSlice.ts # Redux/Zustand slice
│ │ ├── types/ # Feature types
│ │ │ └── auth.types.ts
│ │ └── utils/ # Feature utilities
│ │ └── validation.ts
│ │
│ ├── dashboard/ # Dashboard feature
│ │ ├── components/
│ │ ├── screens/
│ │ ├── hooks/
│ │ ├── services/
│ │ ├── store/
│ │ └── types/
│ │
│ ├── profile/ # Profile feature
│ │ ├── components/
│ │ ├── screens/
│ │ ├── hooks/
│ │ ├── services/
│ │ ├── store/
│ │ └── types/
│ │
│ └── settings/ # Settings feature
│ ├── components/
│ ├── screens/
│ ├── hooks/
│ └── types/
│
├── shared/ # Shared UI & business logic
│ ├── components/ # Reusable UI components
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.styles.ts
│ │ │ ├── Button.test.tsx
│ │ │ └── index.ts
│ │ ├── Card/
│ │ ├── Input/
│ │ ├── Modal/
│ │ ├── LoadingSpinner/
│ │ ├── EmptyState/
│ │ └── index.ts # Barrel export
│ ├── layouts/ # Layout components
│ │ ├── AuthLayout.tsx
│ │ ├── MainLayout.tsx
│ │ └── TabBarLayout.tsx
│ ├── navigation/ # Shared navigation components
│ │ ├── TabBarIcon.tsx
│ │ └── HeaderButton.tsx
│ ├── hooks/ # Shared hooks
│ │ ├── useTheme.ts
│ │ ├── useKeyboard.ts
│ │ └── usePermissions.ts
│ └── theme/ # Theming system
│ ├── ThemeProvider.tsx
│ ├── lightTheme.ts
│ ├── darkTheme.ts
│ └── useTheme.ts
│
├── services/ # Global services
│ ├── analytics/
│ │ ├── AnalyticsService.ts
│ │ └── events.ts
│ ├── crashReporting/
│ │ └── CrashReporter.ts
│ ├── notifications/
│ │ ├── PushNotificationService.ts
│ │ └── localNotifications.ts
│ ├── storage/
│ │ ├── SecureStorage.ts # Encrypted storage (sensitive data)
│ │ └── MMKVStorage.ts # High-performance storage
│ └── cache/
│ ├── QueryCache.ts
│ └── ImageCache.ts
│
├── store/ # Global state management
│ ├── rootReducer.ts # Combined reducers
│ ├── rootSaga.ts # If using Redux-Saga
│ ├── store.ts # Store configuration
│ └── middleware/
│ ├── logger.ts
│ └── persistence.ts
│
├── assets/ # Static assets
│ ├── fonts/
│ │ ├── Inter-Regular.ttf
│ │ ├── Inter-Bold.ttf
│ │ └── index.ts
│ ├── images/
│ │ ├── icons/
│ │ ├── illustrations/
│ │ └── index.ts
│ ├── animations/ # Lottie animations
│ │ └── loading.json
│ └── locales/ # i18n translations
│ ├── en/
│ ├── es/
│ ├── fr/
│ └── i18n.ts
│
└── types/ # Global TypeScript declarations
├── navigation.d.ts
├── env.d.ts
└── images.d.ts
- Clean Architecture in React Native
Implement Clean Architecture within each feature module to separate concerns: Presentation (UI), Domain (business logic), and Data (API/storage). This makes code testable, maintainable, and framework-independent.
src/modules/tasks/ # Example feature with Clean Architecture
├── presentation/ # UI Layer
│ ├── screens/
│ │ ├── TaskListScreen.tsx
│ │ └── TaskDetailScreen.tsx
│ ├── components/
│ │ ├── TaskCard.tsx
│ │ └── TaskFilter.tsx
│ └── viewModels/
│ └── TaskListViewModel.ts # Connects UI to domain
│
├── domain/ # Business Logic Layer (No React Native imports)
│ ├── entities/
│ │ └── Task.ts # Business model
│ ├── useCases/
│ │ ├── GetTasksUseCase.ts
│ │ ├── CreateTaskUseCase.ts
│ │ └── UpdateTaskUseCase.ts
│ ├── repositories/
│ │ └── TaskRepository.ts # Interface (abstraction)
│ └── validators/
│ └── TaskValidator.ts
│
└── data/ # Data Layer
├── repositories/
│ └── TaskRepositoryImpl.ts # Implements TaskRepository
├── dataSources/
│ ├── remote/
│ │ ├── TaskAPI.ts
│ │ └── TaskDTO.ts
│ └── local/
│ ├── TaskLocalStorage.ts
│ └── TaskEntity.ts
└── mappers/
└── TaskMapper.ts # DTO <-> Entity mapping
// Domain Layer (No React Native dependencies) // domain/entities/Task.ts export interface Task { id: string; title: string; description: string; completed: boolean; createdAt: Date; } // domain/repositories/TaskRepository.ts export interface TaskRepository { getTasks(): Promise<Task[]>; createTask(task: Omit<Task, 'id' | 'createdAt'>): Promise<Task>; updateTask(id: string, updates: Partial<Task>): Promise<Task>; deleteTask(id: string): Promise<void>; } // domain/useCases/GetTasksUseCase.ts import { TaskRepository } from '../repositories/TaskRepository'; import { Task } from '../entities/Task'; export class GetTasksUseCase { constructor(private taskRepository: TaskRepository) {} async execute(): Promise<Task[]> { return await this.taskRepository.getTasks(); } } // Data Layer (Implements interfaces) // data/repositories/TaskRepositoryImpl.ts import { TaskRepository } from '../../domain/repositories/TaskRepository'; import { Task } from '../../domain/entities/Task'; import { TaskAPI } from '../dataSources/remote/TaskAPI'; import { TaskMapper } from '../mappers/TaskMapper'; export class TaskRepositoryImpl implements TaskRepository { constructor(private api: TaskAPI) {} async getTasks(): Promise<Task[]> { const dtos = await this.api.fetchTasks(); return dtos.map(TaskMapper.toDomain); } async createTask(task: Omit<Task, 'id' | 'createdAt'>): Promise<Task> { const dto = await this.api.createTask(task); return TaskMapper.toDomain(dto); } async updateTask(id: string, updates: Partial<Task>): Promise<Task> { const dto = await this.api.updateTask(id, updates); return TaskMapper.toDomain(dto); } async deleteTask(id: string): Promise<void> { await this.api.deleteTask(id); } } // Presentation Layer (React components) // presentation/viewModels/TaskListViewModel.ts import { useState, useEffect } from 'react'; import { GetTasksUseCase } from '../../domain/useCases/GetTasksUseCase'; import { Task } from '../../domain/entities/Task'; export const useTaskListViewModel = (getTasksUseCase: GetTasksUseCase) => { const [tasks, setTasks] = useState<Task[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { loadTasks(); }, []); const loadTasks = async () => { try { setLoading(true); const fetchedTasks = await getTasksUseCase.execute(); setTasks(fetchedTasks); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load tasks'); } finally { setLoading(false); } }; return { tasks, loading, error, refetch: loadTasks }; }; // presentation/screens/TaskListScreen.tsx import React from 'react'; import { View, FlatList, Text } from 'react-native'; import { useTaskListViewModel } from '../viewModels/TaskListViewModel'; import { GetTasksUseCase } from '../../domain/useCases/GetTasksUseCase'; import { TaskRepositoryImpl } from '../../data/repositories/TaskRepositoryImpl'; import { TaskAPI } from '../../data/dataSources/remote/TaskAPI'; const TaskListScreen: React.FC = () => { // Dependency injection const api = new TaskAPI(); const repository = new TaskRepositoryImpl(api); const getTasksUseCase = new GetTasksUseCase(repository); const { tasks, loading, error, refetch } = useTaskListViewModel(getTasksUseCase); if (loading) return <LoadingSpinner />; if (error) return <ErrorView message={error} onRetry={refetch} />; return ( <FlatList data={tasks} renderItem={({ item }) => <TaskCard task={item} />} keyExtractor={item => item.id} onRefresh={refetch} refreshing={loading} /> ); };
- State Management Architecture
Choose state management based on app complexity. For 2026, the recommended stacks are: Zustand (simple to complex), Redux Toolkit (large teams, complex state), or React Context + useReducer (small apps).
// store/store.ts – Redux Toolkit configuration import { configureStore } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { persistStore, persistReducer } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; import rootReducer from './rootReducer'; import { api } from '../core/api/baseApi'; const persistConfig = { key: 'root', storage: AsyncStorage, whitelist: ['auth', 'settings'], // Only persist these reducers blacklist: ['navigation'] // Never persist these }; const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'] } }).concat(api.middleware), devTools: process.env.NODE_ENV !== 'production' }); export const persistor = persistStore(store); setupListeners(store.dispatch); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch; // store/slices/authSlice.ts import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import { authService } from '../../modules/auth/services/authService'; import { User } from '../../modules/auth/types/auth.types'; interface AuthState { user: User | null; token: string | null; isLoading: boolean; error: string | null; } const initialState: AuthState = { user: null, token: null, isLoading: false, error: null }; export const login = createAsyncThunk( 'auth/login', async ({ email, password }: { email: string; password: string }) => { const response = await authService.login(email, password); return response; } ); const authSlice = createSlice({ name: 'auth', initialState, reducers: { logout: (state) => { state.user = null; state.token = null; state.error = null; }, clearError: (state) => { state.error = null; } }, extraReducers: (builder) => { builder .addCase(login.pending, (state) => { state.isLoading = true; state.error = null; }) .addCase(login.fulfilled, (state, action) => { state.isLoading = false; state.user = action.payload.user; state.token = action.payload.token; }) .addCase(login.rejected, (state, action) => { state.isLoading = false; state.error = action.error.message || 'Login failed'; }); } }); export const { logout, clearError } = authSlice.actions; export default authSlice.reducer; // hooks/useAppDispatch.ts (typed hooks) import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux'; import type { RootState, AppDispatch } from '../store/store'; export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; // Usage in component import { useAppDispatch, useAppSelector } from '../hooks/useAppDispatch'; import { login } from '../store/slices/authSlice'; const LoginScreen = () => { const dispatch = useAppDispatch(); const { isLoading, error } = useAppSelector(state => state.auth); const handleLogin = () => { dispatch(login({ email, password })); }; };
- Navigation Structure
Use React Navigation 6+ with a clear separation of navigators. Define navigation types for type safety.
// src/app/navigation/types.ts export type RootStackParamList = { Auth: undefined; Main: undefined; Modal: { screen: string }; }; export type AuthStackParamList = { Login: undefined; SignUp: undefined; ForgotPassword: undefined; }; export type MainTabParamList = { Dashboard: undefined; Profile: undefined; Settings: undefined; }; export type DashboardStackParamList = { DashboardHome: undefined; TaskDetail: { taskId: string }; CreateTask: undefined; }; declare global { namespace ReactNavigation { interface RootParamList extends RootStackParamList {} } } // src/app/navigation/RootNavigator.tsx import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { useAppSelector } from '../../hooks/useAppDispatch'; import { AuthNavigator } from './AuthNavigator'; import { MainNavigator } from './MainNavigator'; import { linking } from './linking'; const Stack = createNativeStackNavigator<RootStackParamList>(); export const RootNavigator: React.FC = () => { const { token } = useAppSelector(state => state.auth); const isAuthenticated = !!token; return ( <NavigationContainer linking={linking}> <Stack.Navigator screenOptions={{ headerShown: false }}> {!isAuthenticated ? ( <Stack.Screen name="Auth" component={AuthNavigator} /> ) : ( <Stack.Screen name="Main" component={MainNavigator} /> )} <Stack.Group screenOptions={{ presentation: 'modal' }}> <Stack.Screen name="Modal" component={ModalScreen} /> </Stack.Group> </Stack.Navigator> </NavigationContainer> ); }; // src/app/navigation/AuthNavigator.tsx import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { LoginScreen, SignUpScreen, ForgotPasswordScreen } from '../../modules/auth/screens'; const AuthStack = createNativeStackNavigator<AuthStackParamList>(); export const AuthNavigator = () => ( <AuthStack.Navigator> <AuthStack.Screen name="Login" component={LoginScreen} /> <AuthStack.Screen name="SignUp" component={SignUpScreen} /> <AuthStack.Screen name="ForgotPassword" component={ForgotPasswordScreen} /> </AuthStack.Navigator> ); // src/app/navigation/MainNavigator.tsx import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { DashboardNavigator } from '../../modules/dashboard/navigation/DashboardNavigator'; import { ProfileScreen } from '../../modules/profile/screens/ProfileScreen'; import { SettingsScreen } from '../../modules/settings/screens/SettingsScreen'; import { TabBarIcon } from '../../shared/navigation/TabBarIcon'; const Tab = createBottomTabNavigator<MainTabParamList>(); export const MainNavigator = () => ( <Tab.Navigator screenOptions={({ route }) => ({ tabBarIcon: ({ color, size }) => ( <TabBarIcon routeName={route.name} color={color} size={size} /> ), tabBarActiveTintColor: '#007AFF', tabBarInactiveTintColor: 'gray', headerShown: false })} > <Tab.Screen name="Dashboard" component={DashboardNavigator} /> <Tab.Screen name="Profile" component={ProfileScreen} /> <Tab.Screen name="Settings" component={SettingsScreen} /> </Tab.Navigator> );
- Environment Configuration
Manage multiple environments (development, staging, production) using .env files and react-native-config.
// .env.development API_URL=https://dev-api.example.com API_KEY=dev_key_123 SENTRY_DSN=https://dev@sentry.io/123 ENVIRONMENT=development // .env.staging API_URL=https://staging-api.example.com API_KEY=staging_key_123 SENTRY_DSN=https://staging@sentry.io/123 ENVIRONMENT=staging // .env.production API_URL=https://api.example.com API_KEY=prod_key_123 SENTRY_DSN=https://prod@sentry.io/123 ENVIRONMENT=production // src/core/config/env.ts import Config from 'react-native-config'; interface EnvConfig { apiUrl: string; apiKey: string; sentryDsn: string; environment: 'development' | 'staging' | 'production'; isDevelopment: boolean; isProduction: boolean; isStaging: boolean; } export const env: EnvConfig = { apiUrl: Config.API_URL || '', apiKey: Config.API_KEY || '', sentryDsn: Config.SENTRY_DSN || '', environment: Config.ENVIRONMENT as EnvConfig['environment'], isDevelopment: Config.ENVIRONMENT === 'development', isProduction: Config.ENVIRONMENT === 'production', isStaging: Config.ENVIRONMENT === 'staging' }; // package.json scripts { "scripts": { "start": "react-native start", "ios:dev": "ENVFILE=.env.development react-native run-ios", "ios:staging": "ENVFILE=.env.staging react-native run-ios --configuration Staging", "ios:prod": "ENVFILE=.env.production react-native run-ios --configuration Release", "android:dev": "ENVFILE=.env.development react-native run-android", "android:staging": "ENVFILE=.env.staging react-native run-android --variant=stagingDebug", "android:prod": "ENVFILE=.env.production react-native run-android --variant=release" } }
- Testing Structure
Organize tests alongside source files with clear separation of unit, integration, and E2E tests.
src/
├── modules/
│ └── auth/
│ ├── __tests__/ # Unit & integration tests
│ │ ├── components/
│ │ │ └── LoginForm.test.tsx
│ │ ├── services/
│ │ │ └── authService.test.ts
│ │ └── hooks/
│ │ └── useAuth.test.ts
│ ├── components/
│ └── screens/
│
__tests__/ # Root-level test setup
├── setup.ts # Jest setup
├── mocks/ # Global mocks
│ ├── react-native.ts
│ ├── asyncStorage.ts
│ └── navigation.ts
└── e2e/ # Detox E2E tests
├── config.json
├── auth.spec.js
└── dashboard.spec.js
// jest.config.js
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['<rootDir>/__tests__/setup.ts'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation)/)'
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.styles.ts',
'!src/types/**'
],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
}
};
- Performance Optimization Structure
Organize performance-critical code and implement monitoring from the start.
// src/core/performance/PerformanceMonitor.ts import { InteractionManager } from 'react-native'; class PerformanceMonitor { private marks: Map<string, number> = new Map(); startMark(name: string) { this.marks.set(name, performance.now()); } endMark(name: string) { const start = this.marks.get(name); if (start) { const duration = performance.now() - start; console.log(`⏱️ ${name}: ${duration.toFixed(2)}ms`); this.marks.delete(name); // Send to analytics in production if (env.isProduction) { analytics.trackTiming(name, duration); } } } // Defer non-critical work async runAfterInteractions<T>(task: () => Promise<T>): Promise<T> { return new Promise((resolve) => { InteractionManager.runAfterInteractions(async () => { resolve(await task()); }); }); } } export const performanceMonitor = new PerformanceMonitor(); // Usage in components const TaskListScreen = () => { useEffect(() => { performanceMonitor.startMark('TaskListScreen:load'); loadTasks().finally(() => { performanceMonitor.endMark('TaskListScreen:load'); }); }, []); }; // src/core/performance/LazyLoad.tsx import React, { Suspense, lazy, ComponentType } from 'react'; import { LoadingSpinner } from '../../shared/components/LoadingSpinner'; // Code splitting for large components export const lazyLoad = <T extends ComponentType<any>>( factory: () => Promise<{ default: T }> ) => { const LazyComponent = lazy(factory); return (props: React.ComponentProps<T>) => ( <Suspense fallback={<LoadingSpinner />}> <LazyComponent {...props} /> </Suspense> ); }; // Usage const HeavyDashboard = lazyLoad(() => import('./HeavyDashboardScreen'));
- Monorepo Structure (For Large Teams)
For apps with multiple React Native projects or shared code across web/mobile, use a monorepo with Nx or Turborepo.
my-monorepo/
├── apps/
│ ├── mobile/ # React Native app
│ │ ├── src/
│ │ ├── android/
│ │ ├── ios/
│ │ └── package.json
│ ├── web/ # React web app
│ │ ├── src/
│ │ └── package.json
│ └── backend/ # Node.js backend
│ └── src/
│
├── packages/ # Shared packages
│ ├── shared-ui/ # Cross-platform UI components
│ │ ├── src/
│ │ └── package.json
│ ├── shared-types/ # TypeScript types
│ │ ├── index.ts
│ │ └── package.json
│ ├── shared-hooks/ # Shared React hooks
│ │ └── package.json
│ └── config-eslint/ # Shared ESLint config
│ └── index.js
│
├── tools/ # Build tools & scripts
│ ├── generators/ # Code generators (Plop)
│ └── scripts/
│
├── turbo.json # Turborepo configuration
├── nx.json # Nx configuration
└── package.json # Root package.json
// turbo.json example
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "test/**"]
},
"lint": {},
"dev": {
"cache": false
}
}
}
- Best Practices & Code Organization Rules
Naming Conventions:
- Components: PascalCase (
UserProfile.tsx) - Hooks: camelCase with 'use' prefix (
useAuth.ts) - Utils: camelCase (
formatDate.ts) - Types: PascalCase with
.types.tssuffix (auth.types.ts) - Constants: UPPER_SNAKE_CASE (
API_BASE_URL)
File Organization Rules:
- Each file exports one main component/function
- Keep files under 300 lines
- Group related files in folders
- Use barrel exports (
index.ts) for clean imports
Import Order (enforced by ESLint):
- React and React Native
- Third-party libraries
- Absolute imports (@/core, @/shared)
- Relative imports (./components, ../hooks)
- Styles and assets
Code Splitting Strategy:
- Lazy load screens not in the initial route
- Lazy load heavy third-party libraries
- Use InteractionManager for non-critical work
State Management Guidelines:
- Local UI state: useState/useReducer
- Shared app state: Zustand/Redux
- Server state: React Query (TanStack Query)
- Form state: React Hook Form
Error Boundaries:
- Wrap each feature module with error boundary
- Global error boundary at root
- Log errors to monitoring service (Sentry)
// Example: Barrel exports pattern // src/shared/components/index.ts export { Button } from './Button/Button'; export { Card } from './Card/Card'; export { Input } from './Input/Input'; export { Modal } from './Modal/Modal'; // Usage in feature module import { Button, Card, Input } from '@/shared/components'; // Example: Absolute imports via tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@core/*": ["src/core/*"], "@shared/*": ["src/shared/*"], "@modules/*": ["src/modules/*"], "@assets/*": ["src/assets/*"] } } } // Example: ESLint rule for import order (package.json) { "eslintConfig": { "rules": { "import/order": ["error", { "groups": ["builtin", "external", "internal", "parent", "sibling", "index"], "pathGroups": [ { "pattern": "react", "group": "external", "position": "before" }, { "pattern": "@/**", "group": "internal" } ], "alphabetize": { "order": "asc", "caseInsensitive": true } }] } } }