Production Architecture Guide — 2025

React
Native

Clean Architecture Field Guide

A scalable, production-grade React Native architecture using Zustand, TanStack Query, MMKV, WatermelonDB, Socket.IO, NativeWind — covering offline-first, secure storage, API encryption, real-time, smooth animations, and speech services.

React Native 0.76+ TypeScript Zustand TanStack Query MMKV WatermelonDB Socket.IO Reanimated 3 AES-256 Expo Speech SOLID + KISS
Scroll to explore

Five Layers, One Direction

Unidirectional data flow from UI to Domain to Data. Each layer knows only the layer below it. UI never touches the database; data layer never imports a React component.

01
🖼️
Presentation
Screens, components, hooks, navigation. Pure UI. Never accesses DB or network directly.
02
🧠
State
Zustand stores, TanStack Query cache. Single source of truth. No I/O — delegates to services.
03
⚙️
Domain
Pure TypeScript: entities, use-case functions, repository interfaces. Zero React, zero network.
04
📡
Data
API clients (Axios), repository impls, WatermelonDB models, MMKV, Socket.IO.
05
🔧
Core / Infra
Encryption, DI container (tsyringe), config, logging, error boundaries, theme tokens.
5
Architecture Layers
100%
TypeScript Strict
Offline
First Design
AES-256
Payload Encryption
RT
Real-time Events
60fps
Reanimated 3
Project Structure — src/
📁src/
📁core/// cross-cutting: DI, config, encryption, theme tokens
📁config/
📄AppConfig.ts// typed env vars via react-native-config
📁di/
📄container.ts// tsyringe IOC container bootstrap
📄tokens.ts// injection tokens
📁security/
📄EncryptionService.ts// AES-256-GCM via react-native-aes-crypto
📄KeychainService.ts// react-native-keychain wrapper
📁theme/
📄colors.ts// semantic color tokens
📄typography.ts
📄spacing.ts// 4pt grid constants
📄useTheme.ts// light/dark token resolution
📁errors/
📄AppError.ts// discriminated union error types
📄ErrorBoundary.tsx
📁utils/
📄debounce.ts// typed debounce for search inputs
📄logger.ts// dev + crash reporting

📁domain/// pure TypeScript — zero RN, zero network
📁entities/
📄User.ts
📄AuthToken.ts
📁repositories/// interfaces only
📄IUserRepository.ts
📄IAuthRepository.ts
📁usecases/
📄loginUseCase.ts
📄registerUseCase.ts
📄getUserProfileUseCase.ts

📁data/// impls: Axios, WatermelonDB, MMKV, Socket.IO
📁api/
📄httpClient.ts// Axios + interceptors + cancel tokens
📄authApiService.ts
📄userApiService.ts
📁local/
📄database.ts// WatermelonDB setup
📄UserModel.ts// WatermelonDB model
📄schema.ts
📁repositories/
📄UserRepositoryImpl.ts
📄AuthRepositoryImpl.ts
📁realtime/
📄socketClient.ts// Socket.IO client with reconnect logic
📄eventBus.ts// typed event emitter
📁storage/
📄mmkvStorage.ts// typed MMKV wrapper for fast key-value
📄secureStorage.ts// Keychain/Keystore for tokens

📁state/// Zustand stores + TanStack Query hooks
📄authStore.ts// Zustand — user, token, login/logout actions
📄uiStore.ts// Zustand — theme, loading, toast, modals
📁queries/
📄useUserQuery.ts// TanStack Query — fetching, caching, sync
📄useSearchQuery.ts// debounced search query
📄queryClient.ts// shared QueryClient config

📁presentation/
📁navigation/
📄RootNavigator.tsx// React Navigation 7 — stack + tabs
📄navigationTypes.ts// typed route params
📁components/// design system atoms
📄Button.tsx
📄Input.tsx
📄SkeletonLoader.tsx
📄AnimatedListItem.tsx// Reanimated 3 enter/exit
📁screens/
📁auth/
📄LoginScreen.tsx
📄RegisterScreen.tsx
📁home/
📄HomeScreen.tsx
📄useHomeViewModel.ts// screen-local state + queries
📁hooks/
📄useNetworkStatus.ts
📄useSpeech.ts// STT + TTS abstraction hook
📄useWebSocket.ts
📄useDebounce.ts

📁__tests__/
📁unit/
📁integration/
📁e2e/// Maestro flows
📄tsconfig.json
📄babel.config.js
📄.env.production / .env.development

Each Layer Owns Its Job

Boundaries are enforced by TypeScript interfaces and inversion of control — not by convention or team discipline alone.

01
Presentation Layer 🖼️
Screens, components, navigation — renders state, dispatches intent
Screens are dumb: they subscribe to state slices via Zustand selectors and call use-case hooks. No business logic, no direct API calls. Every screen has a co-located useXxxViewModel.ts hook that assembles the state and handlers the screen needs — keeping JSX files clean. Navigation lives in RootNavigator.tsx with typed params; auth guards are plugin-style route wrappers.
React Navigation 7 NativeWind (Tailwind CSS) Reanimated 3 Shared Element Transitions ViewModel hooks
02
State Layer 🧠
Zustand global state + TanStack Query server cache
Two distinct state concerns: Zustand for client-owned state (auth, theme, UI flags) and TanStack Query for server-synchronized state (fetching, caching, background sync, optimistic updates). They never collide. Zustand stores are sliced by domain — never one giant store. TanStack Query's staleTime and gcTime are configured per query for offline-first resilience.
Zustand slices TanStack Query v5 Optimistic updates Background sync Persist middleware (MMKV)
03
Domain Layer ⚙️
Pure TypeScript — zero React, zero Axios, zero anything
This is the heart. Entities are plain TypeScript interfaces. Repository interfaces define contracts — IUserRepository, IAuthRepository. Use-cases are pure async functions that take repository interfaces as parameters — no class instances, no DI magic, trivially unit-testable. The AppError discriminated union propagates cleanly from domain to UI.
Pure TS entities Repository interfaces Use-case functions AppError union type
04
Data Layer 📡
Axios, WatermelonDB, MMKV, Socket.IO — all I/O lives here
Implements domain repository interfaces. UserRepositoryImpl tries cache first (WatermelonDB), then network. On success, persists to DB. httpClient.ts is an Axios instance with request/response interceptors for JWT injection, AES encryption, cancel-token management, and retry logic. Socket.IO client manages reconnect, heartbeat, and typed event routing.
Axios interceptors WatermelonDB (offline cache) MMKV (fast KV) Keychain (secure) Socket.IO client
05
Core / Infra Layer 🔧
Encryption, DI, theme, error boundaries, config
Everything that's needed everywhere but shouldn't be bundled with business logic. tsyringe is the IOC container — use-case and repository bindings registered here, resolved at runtime. Theme tokens (colors, spacing, typography) as plain TypeScript objects — NativeWind reads them via tailwind.config.ts. react-native-config provides typed environment variables, never hardcoded strings.
tsyringe DI AES-256-GCM service Theme tokens react-native-config ErrorBoundary

Every Concern Solved

Speech-to-text, API encryption, offline-first, cancellable calls with debounce, secure token storage, real-time events, and 60fps animations — none of these are bolted on.

Strategy
Single Axios instance with layered interceptors: JWT injection → AES-256 body encryption → AbortController cancel tokens → exponential-backoff retry (non-2xx). Debounced search queries use a shared useDebouncedQuery hook — one hook, consistent 300ms delay, automatic cancel on unmount.
TypeScript data/api/httpClient.ts
import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios';
import { secureStorage } from '../storage/secureStorage';
import { encryptionService } from '../../core/security/EncryptionService';

const createHttpClient = (): AxiosInstance => {
  const client = axios.create({
    baseURL: AppConfig.API_BASE_URL,
    timeout: 15_000,
  });

  // 1. Inject JWT + AES-encrypt body
  client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
    const token = await secureStorage.getAccessToken();
    if (token) config.headers.Authorization = `Bearer ${token}`;

    if (config.data) {
      config.data = await encryptionService.encryptPayload(config.data);
      config.headers['X-Encrypted'] = '1';
    }
    return config;
  });

  // 2. Decrypt response + handle token refresh
  client.interceptors.response.use(
    async (response) => {
      if (response.headers['x-encrypted']) {
        response.data = await encryptionService.decryptPayload(response.data);
      }
      return response;
    },
    async (error) => {
      if (error.response?.status === 401) {
        await refreshTokenAndRetry(error, client);
      }
      return Promise.reject(toAppError(error));
    }
  );
  return client;
};

// Cancellable request helper
export const cancellableRequest = <T>(
  request: (signal: AbortSignal) => Promise<T>
) => {
  const controller = new AbortController();
  return { promise: request(controller.signal), cancel: () => controller.abort() };
};
TypeScript state/queries/useSearchQuery.ts — debounced
export const useSearchQuery = (rawQuery: string) => {
  const debouncedQuery = useDebounce(rawQuery, 300);

  return useQuery({
    queryKey: ['search', debouncedQuery],
    queryFn: ({ signal }) => userApiService.search(debouncedQuery, signal),
    enabled: debouncedQuery.length > 1,   // skip empty / single char
    staleTime: 30_000,                     // 30s — search cache
    placeholderData: keepPreviousData,      // no flicker between keystrokes
  });
};

// useDebounce — typed, cancels on unmount
export const useDebounce = <T>(value: T, delay: number): T => {
  const [debounced, setDebounced] = useState<T>(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
};
Strategy
WatermelonDB as the offline database (SQLite under the hood, observable records). TanStack Query's networkMode: 'offlineFirst' + persistQueryClient backed by MMKV for query cache persistence across app restarts. NetInfo drives a connectivity Zustand slice — UI degrades gracefully and queues mutations for later sync.
TypeScript data/repositories/UserRepositoryImpl.ts
export class UserRepositoryImpl implements IUserRepository {
  constructor(
    private api: UserApiService,
    private db: Database,
  ) {}

  async getUser(id: string): Promise<User> {
    // 1. Try local cache first
    const local = await this.db.collections
      .get<UserModel>('users').find(id);
    if (local) return toDomain(local);

    // 2. Fetch from network
    const remote = await this.api.getUser(id);

    // 3. Persist to local DB
    await this.db.write(async () => {
      await this.db.collections
        .get<UserModel>('users')
        .create(u => { u._raw.id = remote.id; u.name = remote.name; });
    });
    return remote;
  }
}

// TanStack Query persistence setup
const mmkvPersister = createMMKVStoragePersister({ storage: mmkv });
persistQueryClient({ queryClient, persister: mmkvPersister, maxAge: Infinity });
Strategy
Two-tier storage: react-native-keychain (iOS Keychain / Android Keystore) for tokens — hardware-backed, biometric-accessible. MMKV for non-sensitive fast-access values (theme preference, onboarding flags, last seen user ID). Never store tokens in AsyncStorage or MMKV — those are not encrypted at the OS level.
TypeScript data/storage/secureStorage.ts
import * as Keychain from 'react-native-keychain';

const SERVICE = 'com.app.tokens';

export const secureStorage = {
  async saveTokens(access: string, refresh: string) {
    await Keychain.setGenericPassword(
      'tokens',
      JSON.stringify({ access, refresh }),
      {
        service: SERVICE,
        accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
        accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
      }
    );
  },

  async getAccessToken(): Promise<string | null> {
    const creds = await Keychain.getGenericPassword({ service: SERVICE });
    if (!creds) return null;
    return JSON.parse(creds.password).access;
  },

  async clearTokens() {
    await Keychain.resetGenericPassword({ service: SERVICE });
  },
};

// MMKV — typed wrapper for non-sensitive values
const storage = new MMKV({ id: 'app-prefs' });
export const prefs = {
  setTheme: (mode: 'light' | 'dark') => storage.set('theme', mode),
  getTheme: () => storage.getString('theme'),
  setOnboarded: () => storage.set('onboarded', true),
  isOnboarded: () => storage.getBoolean('onboarded') ?? false,
};
Strategy
AES-256-GCM via react-native-aes-crypto (native module — no JS-land crypto). Session key established via ECDH handshake on app init; stored in Keychain for the session. Every API request body is encrypted before leaving the device; every response body is decrypted in the Axios interceptor — routes are completely unaware.
TypeScript core/security/EncryptionService.ts
import Aes from 'react-native-aes-crypto';

class EncryptionService {
  private sessionKey: string | null = null;

  async initSessionKey(serverPublicKey: string) {
    // ECDH: derive shared secret, store in Keychain
    this.sessionKey = await deriveECDHSecret(serverPublicKey);
    await secureStorage.saveKey(this.sessionKey);
  }

  async encryptPayload(data: unknown): Promise<string> {
    const key = await this.getKey();
    const iv = await Aes.randomKey(16); // 128-bit IV
    const plaintext = JSON.stringify(data);
    const cipher = await Aes.encrypt(plaintext, key, iv, 'aes-256-gcm');
    // Pack as base64: iv.cipher (server splits on first 32 chars)
    return `${iv}.${cipher}`;
  }

  async decryptPayload(packed: string): Promise<unknown> {
    const [iv, cipher] = packed.split('.');
    const key = await this.getKey();
    const plaintext = await Aes.decrypt(cipher, key, iv, 'aes-256-gcm');
    return JSON.parse(plaintext);
  }
}
Strategy
@react-native-voice/voice for Speech-to-Text — streaming partial results via onSpeechPartialResults, final via onSpeechResults. expo-speech for Text-to-Speech (or react-native-tts for bare RN) with language, pitch, rate, and queue control. Both are abstracted behind a useSpeech hook — screens never import native modules directly.
TypeScript presentation/hooks/useSpeech.ts
import Voice, { SpeechResultsEvent } from '@react-native-voice/voice';
import * as Speech from 'expo-speech';

export const useSpeech = () => {
  const [transcript, setTranscript] = useState('');
  const [isListening, setListening] = useState(false);

  useEffect(() => {
    Voice.onSpeechPartialResults = (e: SpeechResultsEvent) =>
      setTranscript(e.value?.[0] ?? '');

    Voice.onSpeechResults = (e: SpeechResultsEvent) => {
      setTranscript(e.value?.[0] ?? '');
      setListening(false);
    };
    return () => { Voice.destroy().then(Voice.removeAllListeners); };
  }, []);

  const startListening = async (locale = 'en-US') => {
    setTranscript('');
    setListening(true);
    await Voice.start(locale);
  };

  const stopListening = async () => {
    await Voice.stop();
    setListening(false);
  };

  const speak = (text: string, language = 'en-US') =>
    Speech.speak(text, { language, pitch: 1.0, rate: 0.9 });

  return { transcript, isListening, startListening, stopListening, speak };
};
Strategy
Socket.IO client with automatic reconnect and typed event system. A singleton socketClient.ts manages the connection lifecycle. Events are typed via a shared ServerToClientEvents interface (matched to the backend). A useWebSocket hook subscribes to events in components and handles cleanup. Disconnection triggers an offline flag in Zustand; reconnection triggers a TanStack Query invalidation burst.
TypeScript data/realtime/socketClient.ts
import { io, Socket } from 'socket.io-client';

interface ServerToClientEvents {
  notification: (payload: { title: string; body: string }) => void;
  data_update:  (payload: { entity: string; id: string }) => void;
}

let socket: Socket<ServerToClientEvents> | null = null;

export const connectSocket = (token: string) => {
  socket = io(AppConfig.WS_URL, {
    auth: { token },
    reconnection: true,
    reconnectionDelay: 1000,
    reconnectionDelayMax: 30_000,
    reconnectionAttempts: Infinity,
    transports: ['websocket'],
  });

  socket.on('connect', () => useSocketStore.getState().setConnected(true));
  socket.on('disconnect', () => useSocketStore.getState().setConnected(false));

  socket.on('data_update', ({ entity, id }) => {
    queryClient.invalidateQueries({ queryKey: [entity, id] });
  });
};

// Hook for component-level subscriptions
export const useWebSocket = <K extends keyof ServerToClientEvents>(
  event: K, handler: ServerToClientEvents[K]
) => {
  useEffect(() => {
    socket?.on(event, handler as any);
    return () => { socket?.off(event, handler as any); };
  }, [event, handler]);
};
Strategy
React Native Reanimated 3 for all motion — runs on the UI thread, never the JS thread. Shared Element Transitions via react-native-shared-element (or Reanimated 3's Layout Animations for simple cases). useAnimatedStyle + withSpring / withTiming for gesture-driven interactions. FlatList items enter via FadeInDown layout animation. No Animated API — Reanimated 3 everywhere.
TypeScript presentation/components/AnimatedListItem.tsx
import Animated, {
  FadeInDown, FadeOutUp,
  useAnimatedStyle, useSharedValue,
  withSpring, withTiming, interpolateColor
} from 'react-native-reanimated';

export const AnimatedListItem = ({ item, index }: Props) => {
  const pressed = useSharedValue(0);

  const animStyle = useAnimatedStyle(() => ({
    transform: [{ scale: withSpring(pressed.value ? 0.96 : 1, { damping: 15 }) }],
    backgroundColor: interpolateColor(
      pressed.value, [0, 1],
      ['transparent', 'rgba(251,191,36,0.08)']
    ),
  }));

  return (
    <Animated.View
      entering={FadeInDown.delay(index * 40).springify().damping(14)}
      exiting={FadeOutUp.duration(200)}
      style={animStyle}
    >
      <Pressable
        onPressIn={() => { pressed.value = 1; }}
        onPressOut={() => { pressed.value = 0; }}
      >
        <Text>{item.title}</Text>
      </Pressable>
    </Animated.View>
  );
};
TypeScript Shared Element Hero Transition
// List screen — tag the shared element
import { SharedElement } from 'react-native-shared-element';

<SharedElement id={`user.avatar.${user.id}`}>
  <Image source={{ uri: user.avatarUrl }} style={styles.avatar} />
</SharedElement>

// Detail screen — same id, Reanimated handles the morph
<SharedElement id={`user.avatar.${userId}`}>
  <Image source={{ uri: user.avatarUrl }} style={styles.heroBanner} />
</SharedElement>
🎤
Speech-to-Text / Text-to-Speech
Google Speech via @react-native-voice/voice with streaming partial results. expo-speech for TTS with language, pitch, rate. Both hidden behind useSpeech() — swap the native module without touching screens.
@react-native-voice/voiceexpo-speech
📦
Offline-First (WatermelonDB)
WatermelonDB (SQLite + observables) as local cache. Repository reads local first, fetches remote on miss, persists back. TanStack Query's persistQueryClient backed by MMKV survives app restarts.
@nozbe/watermelondbreact-native-mmkv
🧠
Zustand + TanStack Query
Zustand for client state (auth, UI), TanStack Query v5 for server state. No Redux boilerplate. Query keys typed as const tuples. Background sync, optimistic updates, and automatic stale-while-revalidate.
zustand ^5.x@tanstack/react-query ^5.x
🔑
Secure Token Storage
iOS Keychain / Android Keystore via react-native-keychain. Biometric-gated access, WHEN_UNLOCKED_THIS_DEVICE_ONLY. MMKV for non-sensitive prefs. Never AsyncStorage for secrets.
react-native-keychain ^9.xreact-native-mmkv ^3.x
🔄
Real-time (Socket.IO)
Typed Socket.IO client. Auto-reconnect with exponential backoff. data_update events trigger TanStack Query invalidations — UI updates automatically without manual polling. useWebSocket() hook for component subscriptions.
socket.io-client ^4.x
Reanimated 3 Animations
UI-thread animations, 60fps guaranteed. Layout Animations (FadeInDown, staggered) for lists. Shared Element Transitions for hero navigation. spring-based press feedback. Zero Animated API usage.
react-native-reanimated ^3.xreact-native-shared-element
🎨
NativeWind Design System
Tailwind CSS utility classes mapped to StyleSheet via NativeWind 4. Semantic color tokens in tailwind.config.ts. No hardcoded hex values in components. Dark mode via colorScheme — one class change, both themes.
nativewind ^4.xtailwindcss ^3.x
🧩
Dependency Injection (tsyringe)
Microsoft tsyringe IOC container. @injectable() + @inject(TOKEN) annotations. Repository bindings swapped in tests — no module mocking. Container bootstrapped once at app startup in container.ts.
tsyringe ^4.xreflect-metadata
🌐
React Navigation 7
Type-safe navigation with RootStackParamList. Auth flow guards using useNavigationContainerRef. Deep link support. Screen transition animations via Reanimated. Tab navigator with NativeWind-styled tab bar.
@react-navigation/native ^7.x@react-navigation/native-stack
🔒
AES-256-GCM Payload Encryption
Native module — no JS crypto. Session key via ECDH exchange stored in Keychain. Axios interceptor encrypts/decrypts transparently. GCM provides authenticity check — tampered responses are rejected before reaching domain.
react-native-aes-crypto
🛰️
Network Awareness
@react-native-community/netinfo drives a Zustand connectivity slice. Offline banner auto-shown. Mutations queued in TanStack Query via networkMode: 'offlineFirst'. Re-sync burst on reconnect via queryClient.invalidateQueries().
@react-native-community/netinfo ^11.x
🧪
Testing Strategy
Jest + RNTL for unit/component tests. Domain use-cases are pure functions — zero mocking required. Repositories tested with in-memory fakes. E2E with Maestro — YAML flows, screenshot assertions. CI runs all three tiers.
@testing-library/react-nativemaestro

A Data Request's Journey

From a screen component dispatching an intent to pixels updating on screen — every hop is traceable and testable.

HTTP Request Pipeline

1
Screen → ViewModel hook
User action calls viewModel.doSomething(). ViewModel invokes a use-case from DI container.
UI
2
Use Case execution
Pure async function: validates input, calls repository interface. No I/O itself.
Domain
3
Repository — cache-aside
Checks WatermelonDB first. On miss, delegates to API service.
Data
4
Axios interceptor chain
JWT injected → body AES-encrypted → request dispatched with AbortSignal.
Infra
5
Response interceptor
Body decrypted → mapped to domain entity → persisted to WatermelonDB.
Infra
6
TanStack Query cache update
Result stored in query cache. All subscribers (screens) re-render via selector equality.
State
7
Reanimated layout update
New items animate in via FadeInDown. Removed items FadeOutUp. UI-thread — zero JS-thread jank.
UI

Real-time Event Lifecycle

App starts → connectSocket()
JWT retrieved from Keychain, passed as Socket.IO auth param. Connection established over WSS.
Connection confirmed
Zustand socketStore.setConnected(true). UI can show real-time indicator badge.
Server emits data_update
Typed event received: { entity: 'user', id: '42' }.
TanStack Query invalidation
queryClient.invalidateQueries({ queryKey: ['user', '42'] }) — triggers refetch for all active subscribers.
Screen auto-updates
All screens subscribed to that query key re-render with fresh data. No manual setState anywhere.
Disconnect → reconnect
Socket.IO reconnects with exponential backoff. On reconnect: full invalidateQueries() burst catches missed events.

SOLID + KISS in React Native

Each principle maps to a concrete decision in this codebase — not a poster on the wall.

S
Single Responsibility
LoginScreen.tsx renders — it does not validate credentials. loginUseCase.ts authenticates — it doesn't know about navigation. EncryptionService encrypts — it doesn't send requests. Every file has one reason to change.
O
Open / Closed
Add a new feature by adding a new folder — never modifying domain. New Axios interceptors are added, never existing ones modified. New WsMessage event types extend the typed interface without touching handlers.
L
Liskov Substitution
Any IUserRepository implementation drops into use cases — UserRepositoryImpl, FakeUserRepository in tests, or a future GraphQL impl. The use case never knows the difference.
I
Interface Segregation
IAuthRepository exposes only login / logout / refresh. It's not a god CRUD interface. useSpeech() exposes only what a screen needs — not the raw Voice module API with its 20 callbacks.
D
Dependency Inversion
Screens depend on ViewModel hooks (abstraction). Use cases depend on repository interfaces (abstraction). Nothing in domain knows Axios, Socket.IO, or WatermelonDB exist. tsyringe wires the concretions.
K
Keep It Simple
useDebounce is 8 lines — not a library. socketClient is a module singleton — not an actor system. AppError is a discriminated union — not a class hierarchy. Complexity is borrowed; simplicity is earned.

The Definitive Stack

No Redux. No AsyncStorage for tokens. No moment.js. No class components. Proven, actively-maintained, TypeScript-first picks only.

PackageVersionPurposeLayer
react-native0.76+New Architecture (Fabric + JSI) enabled by default — faster bridge, synchronous native callsui
react-native-reanimated^3.16UI-thread animations via JSI — 60fps guaranteed. Layout Animations, Gestures, SharedValuesui
react-native-gesture-handler^2.21Native gesture system for swipes, pans, and complex multi-touch interactionsui
react-native-shared-element^0.8Hero / shared element transitions between screens — native bridged morphingui
nativewind^4.xTailwind CSS for React Native — semantic tokens, dark mode via colorScheme, zero StyleSheetui
@react-navigation/native^7.xTyped navigation — native-stack (fully native transitions), bottom-tabs, deep linksnav
@react-navigation/native-stack^7.xNative screen transitions via RNScreens — avoids JS-driven re-renders on navigationnav
zustand^5.xMinimal global state — sliced stores, MMKV persist middleware, devtools via Redux DevTools bridgestate
@tanstack/react-query^5.xServer state, caching, background sync, optimistic updates, offline persistQueryClientstate
axios^1.7HTTP client with full interceptor chain, AbortController cancel tokens, transform pipelinedata
@nozbe/watermelondb^0.27Observable SQLite ORM — offline-first, reactive queries, sync protocol for server syncdata
react-native-mmkv^3.x10x faster than AsyncStorage — JSI-based key-value store. TanStack Query persister. Preferences.data
react-native-keychain^9.xiOS Keychain + Android Keystore. Biometric-gated access. Only place tokens are stored.security
react-native-aes-crypto^2.xNative AES-256-GCM encryption — no JS-land crypto. Used by EncryptionService for API payload encryptionsecurity
socket.io-client^4.8Typed real-time WebSocket client — auto-reconnect, namespaces, binary supportreal-time
@react-native-voice/voice^3.xGoogle Speech-to-Text — streaming partial results, multi-language, on-device or cloudui
expo-speech^13.xText-to-Speech — language, pitch, rate, voice selection. Works in bare RN too.ui
react-native-config^1.5Typed .env variable injection — API URLs, feature flags, never hardcodeddata
@react-native-community/netinfo^11.xNetwork status stream — drives offline banner, mutation queuing, re-sync on reconnectdata
tsyringe^4.xMicrosoft IOC container with TypeScript decorators — zero magic, swappable bindings in testsdata
react-native-screens^4.xNative screen containers — reduces memory, enables native transitions, required by React Navigationnav
@testing-library/react-native^13.xAccessibility-first component testing — query by role, text, testId. Pairs with Jestdev
maestrolatestYAML-driven E2E — tap, swipe, assert text. Runs on real devices in CI. No Detox setup hell.dev
@sentry/react-native^6.xCrash reporting, breadcrumbs, performance tracing, source map upload for symbolicated tracesdev

Generate with AI

Paste into Claude, ChatGPT, Gemini, or any AI agent to scaffold this exact architecture from scratch.

 Architecture Prompt
You are a senior React Native architect. Scaffold a production-ready cross-platform app.

Stack:
- Framework: React Native 0.74+ (New Architecture enabled)
- Language: TypeScript strict mode
- Navigation: React Navigation v6 (Stack, Tab, Drawer)
- State: Zustand (global) + React Query (server state)
- Forms: React Hook Form + Zod validation
- Network: Axios + React Query with offline support
- Local: MMKV storage + WatermelonDB (complex local data)
- Styling: StyleSheet + NativeWind (Tailwind)
- Testing: Jest + React Native Testing Library + Detox (E2E)
- CI/CD: EAS Build + EAS Submit (Expo Application Services)

Provide:
1. Feature-based folder structure (features/, shared/, navigation/, store/)
2. Navigation stack with typed route params
3. React Query setup with global error/loading handling
4. Zustand store with persist middleware (MMKV)
5. Custom hook pattern for API integration
6. Form with React Hook Form + Zod schema validation
7. Jest unit test for a custom hook + mock setup
8. EAS build profile configuration (development/preview/production)
crafted with by Sam
 Copied to clipboard!