PraisePraise Emerenini

Mastering MVVM in React Native (Expo SDK 54)

13 September 2025 - 10 min(s) read

Mastering MVVM Architecture in React Native (Expo SDK 54)

As mobile apps scale, state management and separation of concerns become essential.
Adopting the MVVM (Model–View–ViewModel) pattern in React Native helps you maintain clean, testable, and maintainable code — especially when working with frameworks like Expo SDK 54.

What is MVVM?

MVVM stands for:

  • Model → Business logic & data sources (API, DB, storage).
  • View → UI components built using React Native (screens, widgets).
  • ViewModel → Connects Model and View, handles state & exposes data for rendering.

In React Native, you can implement the ViewModel layer using:

  • Zustand, Recoil, or Jotai for global state.
  • React Context + Hooks for local state management.
  • Or even Redux Toolkit for larger apps.

MVVM Data Flow in React Native

Here’s a simple conceptual diagram showing the data flow in MVVM for React Native:

     ┌───────────────────────────────┐
     │           User Input          │
     │     (Press, Type, Gesture)    │
     └───────────────┬───────────────┘
                     │
                     ▼
             ┌──────────────┐
             │  ViewModel   │  ←── Handles Logic + State
             │ (Hooks/Store)│
             └──────┬───────┘
                    │
     ┌──────────────┴──────────────┐
     │          Model              │
     │ (API, DB, Async Storage)    │
     └──────────────┬──────────────┘
                    │
                    ▼
             ┌──────────────┐
             │    View       │  ←── React Components
             │ (UI Render)   │
             └──────────────┘

The View observes data changes from the ViewModel, and the ViewModel fetches or updates data via the Model.
Whenever the state in the ViewModel changes, React automatically re-renders the View.

A scalable project layout for MVVM with Expo:

src/
 ├── models/
 │    └── user.model.ts
 ├── services/
 │    └── user.service.ts
 ├── viewmodels/
 │    └── user.viewmodel.ts
 ├── views/
 │    └── UserScreen.tsx
 ├── hooks/
 │    └── useViewModel.ts
 ├── App.tsx

Example: Managing User Profile (MVVM in React Native)

Let’s go through a simple example that demonstrates fetching and updating user data.

Model – user.model.ts

// src/models/user.model.ts
export interface User {
  id: string;
  name: string;
  email: string;
}

Service Layer – user.service.ts

This simulates your data source (could be an API, local DB, or async storage).

// src/services/user.service.ts
import { User } from "../models/user.model";

export const fetchUser = async (): Promise<User> => {
  // Mock API call
  await new Promise((res) => setTimeout(res, 500));
  return { id: "1", name: "Praise CE", email: "praise@example.com" };
};

export const saveUser = async (user: User): Promise<void> => {
  // Save to backend or local storage
  await new Promise((res) => setTimeout(res, 500));
  console.log("✅ User saved:", user);
};

ViewModel – user.viewmodel.ts

We’ll use Zustand to store and manage state reactively.

// src/viewmodels/user.viewmodel.ts
import { create } from "zustand";
import { fetchUser, saveUser } from "../services/user.service";
import { User } from "../models/user.model";

interface UserState {
  user: User | null;
  loading: boolean;
  fetchUser: () => Promise<void>;
  updateUser: (updates: Partial<User>) => Promise<void>;
}

export const useUserViewModel = create<UserState>((set, get) => ({
  user: null,
  loading: false,

  fetchUser: async () => {
    set({ loading: true });
    const user = await fetchUser();
    set({ user, loading: false });
  },

  updateUser: async (updates) => {
    const current = get().user;
    if (!current) return;
    const updated = { ...current, ...updates };
    set({ user: updated });
    await saveUser(updated);
  },
}));

View – UserScreen.tsx

// src/views/UserScreen.tsx
import React, { useEffect, useState } from "react";
import { View, Text, TextInput, Button, ActivityIndicator } from "react-native";
import { useUserViewModel } from "../viewmodels/user.viewmodel";

export const UserScreen = () => {
  const { user, loading, fetchUser, updateUser } = useUserViewModel();
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  useEffect(() => {
    fetchUser();
  }, []);

  useEffect(() => {
    if (user) {
      setName(user.name);
      setEmail(user.email);
    }
  }, [user]);

  if (loading) return <ActivityIndicator size="large" />;

  return (
    <View style={{ padding: 20 }}>
      <Text style={{ fontSize: 22, fontWeight: "600", marginBottom: 10 }}>Profile</Text>
      <TextInput
        placeholder="Name"
        value={name}
        onChangeText={setName}
        style={{ borderWidth: 1, marginBottom: 10, padding: 8 }}
      />
      <TextInput
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        style={{ borderWidth: 1, marginBottom: 10, padding: 8 }}
      />
      <Button
        title="Save"
        onPress={() => updateUser({ name, email })}
        disabled={loading}
      />
    </View>
  );
};

MVVM + AsyncStorage (Data Persistence Example)

To persist user data between sessions, integrate AsyncStorage:

// src/services/user.service.ts (extended)
import AsyncStorage from "@react-native-async-storage/async-storage";
import { User } from "../models/user.model";

const USER_KEY = "app_user";

export const fetchUser = async (): Promise<User> => {
  const saved = await AsyncStorage.getItem(USER_KEY);
  if (saved) return JSON.parse(saved);
  return { id: "1", name: "Guest", email: "guest@example.com" };
};

export const saveUser = async (user: User) => {
  await AsyncStorage.setItem(USER_KEY, JSON.stringify(user));
};

Why MVVM Works So Well in React Native

  • Decoupled logic: UI and business logic live in separate layers.
  • Reusability: The ViewModel can power multiple screens.
  • Testing: You can test logic without rendering UI.
  • Scalability: Ideal for large, data-driven apps.

Key Takeaways

  • MVVM helps organize React Native apps for growth and maintainability.
  • Zustand, Recoil, or Context Hooks can be used as your ViewModel state layer.
  • AsyncStorage, SQLite, or APIs act as your data Model layer.
  • The View simply observes and reacts to ViewModel changes.

💡 Expo SDK 54 provides excellent TypeScript, HMR, and local storage support — making MVVM easier and faster to implement than ever.

5 tag(s) on post "Mastering MVVM in React Native (Expo SDK 54)"

Staff

Want to collaborate on a future forward project with us?