Blog featured image
Technology Blogs

Building a Scalable React Native App: The Expo + MVVM Hybrid Approach

TL;DR

Combining Expo Router’s file-based navigation with MVVM architecture creates a scalable React Native foundation. This hybrid approach provides modern DX (Expo simplicity) with enterprise structure (strict separation of concerns), plus high-performance tools like MMKV, i18next, and theme-aware components built in.

    Starting Right: The Boilerplate Difference

    In the ever-evolving world of mobile development, starting with a solid foundation is not just an advantage—it’s a necessity. The reactnative-expo-boilerplate-ts is designed to bridge the gap between rapid prototyping and long-term maintainability.
    By combining the modern simplicity of Expo Router with the structural rigor of the MVVM (Model-View-ViewModel) pattern, we’ve created a foundation that’s built for scale, performance, and developer happiness.

    The Core Philosophy: Why Hybrid?

    Most boilerplates force a choice: ease of use (Expo) or architectural strictness (Native/Custom). The hybrid approach chooses both.

    1. Navigation with Expo Router

    Expo Router brings file-based navigation to React Native, mirroring the web experience (like Next.js). This makes it intuitive for newcomers while providing a truly native feel on mobile.
    Key benefits:

  • Deep linking works out of the box
  • File-based structure in `src/app` is self-documenting
  • Complex navigation nesting becomes simple
  • Developer experience matches modern web conventions
  • “`typescript
    // src/app/(tabs)/home.tsx
    // Automatically becomes a route at /home
    export default function HomeScreen() {
    return {/* content */};
    }
    “`

    2. Architecture with MVVM

    While Expo handles navigation flow, MVVM handles application logic and state flow. The pattern organizes code into three layers:
    View — Pure React components

  • Responsible for UI rendering only
  • Consume data from ViewModel hooks
  • Zero awareness of business logic
  • Easy to test with mock ViewModels
  • ViewModel — Custom React hooks

  • Encapsulate state and side effects
  • Handle user interactions
  • Coordinate with Models
  • Examples: `useHomeViewModel`, `useAuthViewModel`
  • Highly testable and reusable
  • Model — Data structures and services

  • API services
  • Data transformations
  • Centralized stores (like `authStore`)
  • Business logic that’s not UI-specific
  • This separation means:

  • Views are dumb and easy to debug
  • ViewModels are testable without UI rendering
  • Models are framework-agnostic
  • High-Performance Features Built-In

    A great boilerplate isn’t just about folder structure—it’s about the foundational tools within.

    Storage: MMKV for Synchronous State

    AsyncStorage is good, but slow. React Native MMKV is a C++ key-value store that’s orders of magnitude faster and—critically—synchronous.
    Why this matters:

  • Read auth state during initial render (no loading states)
  • Theme preferences load instantly (no “flash” of wrong color)
  • Eliminates the awkward “app initializing” experience
  • “`typescript
    const authStore = new MMKV();
    // During app startup, we can read synchronously
    const token = authStore.getString(‘authToken’);
    // No delays, no promises—data is ready immediately
    “`

    Localization: i18next Pre-Configured

    Internationalization is often bolted on after the fact. Here, it’s first-class:

  • Pre-configured `src/i18n` folder with language JSON files
  • React i18next integration for seamless runtime language switching
  • Custom hooks for dynamic language changes
  • Adding a new language is as simple as a new JSON file
  • “`typescript
    // src/i18n/en.json
    {
    “home”: {
    “title”: “Welcome”,
    “subtitle”: “Start building”
    }
    }
    // In components
    const { t } = useTranslation();
    return {t(‘home.title’)};
    “`

    Theming: Light & Dark Mode with System Awareness

    A custom `useThemeColors` hook provides a centralized design system:

  • Responds to system light/dark mode preferences
  • Allows manual override by user
  • Single source of truth for all colors
  • Ensures consistent branding across screens
  • “`typescript
    const colors = useThemeColors();
    return ;
    “`

    The Development Experience

    Zero-Config Onboarding

    New developers can:

  • Clone the repo
  • Run `npm install`
  • Run `npm start`
  • Start coding immediately
  • No complex setup, no mysterious environment variables, no “why doesn’t this work on my machine?”

    Consistency Across the Team

    Several tools work together to maintain code quality:

  • Absolute path aliasing (`@/components`, `@/features`) makes imports clean and refactoring simple
  • ESLint catches common mistakes
  • Prettier formats code consistently
  • Husky runs pre-commit hooks, preventing bad code from reaching the repo
  • Result: Every PR looks like it was written by the same person.

    Scalability Built In

    The feature-first folder structure grows with your app:
    “`
    src/
    ├── features/
    │ ├── home/
    │ │ ├── screens/
    │ │ ├── viewmodels/
    │ │ ├── components/
    │ │ └── styles/
    │ ├── auth/
    │ ├── profile/
    │ └── …
    ├── components/ (shared components)
    ├── services/ (API, storage)
    ├── i18n/ (translations)
    └── theme/ (design system)
    “`
    Even with 100+ screens, the structure remains clean and navigable.

Ready to Build Scalable Apps? Contact Us.

    Best Practices Baked In

    Strict Type Safety with TypeScript

    TypeScript is configured to be helpful without being oppressive:

  • Strict mode for type safety
  • Intelligent autocomplete
  • Catch errors before runtime
  • Self-documenting code through types
  • “`typescript
    interface HomeViewModelState {
    items: Item[];
    loading: boolean;
    error: string | null;
    }
    const useHomeViewModel = (): HomeViewModelState => {
    // TypeScript ensures this hook returns the right shape
    };
    “`

    Native Power When Needed

    React Native’s bridge is great, but sometimes you need native performance. The boilerplate is ready for:

  • Nitro Modules for high-performance native code
  • Turbo Modules for fast native interop
  • Examples already included in `src/native`
  • Component-Driven Development

    Large features use small, focused components:

  • `src/components/shared/` for reusable UI components
  • Feature-specific components stay in their feature folder
  • Each component has a single responsibility
  • Easy to test, easy to reuse
  • Testing Structure

    The boilerplate includes:

  • Jest pre-configured for unit tests
  • React Native Testing Library setup for component tests
  • Example tests showing how to test ViewModels and components
  • Test files live next to source files (`*.test.ts`)
  • Real-World Patterns

    Handling Forms with MVVM

    “`typescript
    // ViewModel
    const useLoginViewModel = () => {
    const [email, setEmail] = useState(”);
    const [password, setPassword] = useState(”);
    const [loading, setLoading] = useState(false);
    const login = async () => {
    setLoading(true);
    try {
    const response = await authService.login(email, password);
    authStore.set(‘token’, response.token);
    } catch (error) {
    // Handle error
    } finally {
    setLoading(false);
    }
    };
    return { email, setEmail, password, setPassword, login, loading };
    };
    // View
    export function LoginScreen() {
    const { email, setEmail, password, setPassword, login, loading } = useLoginViewModel();
    return (



coma
    The reactnative-expo-boilerplate-ts isn’t just a template—it’s a statement on how modern React Native apps should be built. It provides the speed and developer experience of Expo with the architectural confidence of a custom-built enterprise solution.
    By starting with this foundation, your team gets:

  • Immediate productivity (no setup headaches)
  • Long-term maintainability (clear structure scales)
  • Code quality (linting, formatting, testing built in)
  • Performance (MMKV, optimized rendering)
  • Internationalization (i18next ready)
  • Beautiful UI (theme system included)
  • The best part? You’re not locked into this structure. As your app grows and needs change, the boilerplate is flexible enough to adapt.
    Happy coding! 🚀

Q: When should I use MVVM vs just using Redux/Zustand for state management?

A: MVVM with custom hooks works great for small-to-medium apps (< 50 screens). For larger apps with complex shared state, consider Zustand or Redux for global state, while keeping MVVM for screen-specific logic. The boilerplate can support both approaches.

Q: Can I use this boilerplate with Expo Go (the managed service)?

A: Mostly yes. The boilerplate uses mostly compatible libraries, but some native modules might require building with EAS. For rapid prototyping, Expo Go works fine. For production, use EAS build or native compilation.

Q: Is MMKV truly necessary, or can I stick with AsyncStorage?

A: AsyncStorage works fine for small apps, but MMKV’s synchronous nature prevents UI flashes during startup. If you care about perceived performance and smooth theme/auth loading, MMKV is worth it. The switch is painless.

Q: How do I organize API calls in this architecture?

A: Create service files in `src/services/` (e.g., `userService.ts`). ViewModels call these services, receive data, and manage state. Services stay independent of React, making them testable and reusable.

Your Questions Answered

A: MVVM with custom hooks works great for small-to-medium apps (< 50 screens). For larger apps with complex shared state, consider Zustand or Redux for global state, while keeping MVVM for screen-specific logic. The boilerplate can support both approaches.

A: Mostly yes. The boilerplate uses mostly compatible libraries, but some native modules might require building with EAS. For rapid prototyping, Expo Go works fine. For production, use EAS build or native compilation.

A: AsyncStorage works fine for small apps, but MMKV’s synchronous nature prevents UI flashes during startup. If you care about perceived performance and smooth theme/auth loading, MMKV is worth it. The switch is painless.

A: Create service files in `src/services/` (e.g., `userService.ts`). ViewModels call these services, receive data, and manage state. Services stay independent of React, making them testable and reusable.

Read More Similar Blogs

Let’s Transform
Healthcare,
Together.

Partner with us to design, build, and scale digital solutions that drive better outcomes.

Location

5900 Balcones Dr, Ste 100-7286, Austin, TX 78731, United States

Contact form