reactnative
/

React Native Project Structure – Scalable Architecture for 2026

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

reactnative

React Native Project Structure – Scalable Architecture for 2026

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.

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

TEXTRead-only
1
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

  1. Source Code Structure (src/)

The src/ folder contains all application code organized by features and layers. This structure scales from 1 to 100+ developers.

TEXTRead-only
1
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

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

TEXTRead-only
1
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
TypeScriptRead-only
1
// 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}
    />
  );
};

  1. 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).

TypeScriptRead-only
1
// 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 }));
  };
};

  1. Navigation Structure

Use React Navigation 6+ with a clear separation of navigators. Define navigation types for type safety.

TypeScriptRead-only
1
// 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>
);

  1. Environment Configuration

Manage multiple environments (development, staging, production) using .env files and react-native-config.

TypeScriptRead-only
1
// .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"
  }
}

  1. Testing Structure

Organize tests alongside source files with clear separation of unit, integration, and E2E tests.

TEXTRead-only
1
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
    }
  }
};

  1. Performance Optimization Structure

Organize performance-critical code and implement monitoring from the start.

TypeScriptRead-only
1
// 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'));

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

TEXTRead-only
1
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
    }
  }
}

  1. 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.ts suffix (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):

  1. React and React Native
  2. Third-party libraries
  3. Absolute imports (@/core, @/shared)
  4. Relative imports (./components, ../hooks)
  5. 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)
TypeScriptRead-only
1
// 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 }
      }]
    }
  }
}

Test Your Knowledge

Q1
of 4

Which architecture pattern separates Presentation, Domain, and Data layers within features?

A
MVC
B
Clean Architecture
C
MVVM
D
Flux
Q2
of 4

What should you use to manage server state (API data) in React Native?

A
Redux
B
Zustand
C
React Query
D
Context API
Q3
of 4

Which file extension should be used for TypeScript type definitions?

A
.types.ts
B
.d.ts
C
.tsdef
D
.model.ts
Q4
of 4

What is the recommended way to lazy load screens for performance?

A
React.lazy + Suspense
B
require()
C
import() with useEffect
D
AsyncComponent

Frequently Asked Questions

Should I use feature-based or layer-based folder structure?

Feature-based is preferred for large apps (10+ screens). It scales better because each feature is self-contained. Layer-based (components/, screens/, services/) works for small apps but becomes unwieldy as the app grows. For 2026, use feature-based with Clean Architecture layers inside each feature.

How do I handle dependency injection in React Native?

React Native doesn't have built-in DI. Use Context API for service dependencies, or libraries like tsyringe or inversify. For Clean Architecture, pass dependencies through constructors (manual DI) or use a simple Service Locator pattern. Example: const useCase = new GetTasksUseCase(repository);

What's the best state management for large React Native apps?

Zustand is the current recommendation (2026) – simple, performant, and works great with React Native. Redux Toolkit is still excellent for very large teams with complex state logic. Avoid MobX for new projects. Use React Query for server state regardless of your client state choice.

How do I organize shared components?

Create a shared/components/ folder with subfolders for each component. Each component should have its own folder with .tsx, .styles.ts, .test.tsx, and index.ts files. Export everything from a barrel file (index.ts). Avoid generic 'CommonComponents' – name them specifically.

Previous

react native setup

Next

react native components

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.