Skip to main content

Introduction

New Expensify is a cross-platform expense management application built with React Native, enabling users to track expenses, manage reports, communicate with teams, and integrate with accounting systems. The application runs on iOS, Android, Web, and Desktop platforms from a single codebase.

Technology Stack

Core Framework

  • React Native: Cross-platform mobile framework
  • TypeScript: Type-safe JavaScript with strict mode enabled
  • React Navigation: Navigation library for routing and deep linking

State Management

  • React Native Onyx: Custom offline-first state management library
  • Supports optimistic updates and automatic persistence
  • Centralized key definitions in src/ONYXKEYS.ts

Build & Development

  • Webpack: Web bundling
  • Metro: React Native bundling for iOS/Android
  • Hermes: JavaScript engine for React Native

Architectural Principles

1. Offline-First Design

New Expensify is architected to work seamlessly offline. All user actions are designed to function without an internet connection:
  • Optimistic Updates: UI updates immediately before server confirmation
  • Request Queue: API requests are queued and replayed when online
  • Conflict Resolution: Built-in strategies for handling data conflicts
  • Local Persistence: All critical data is persisted locally via Onyx
See Offline-First Architecture for details.

2. Cross-Platform Consistency

The same codebase powers all platforms:
// Example: Platform-specific code when needed
import {Platform} from 'react-native';

const styles = StyleSheet.create({
  container: {
    padding: 16,
    ...Platform.select({
      web: {maxWidth: 800},
      default: {},
    }),
  },
});

3. Component-Based Architecture

The UI is built from reusable components organized in src/components/:
  • Atomic Design: Small, composable components
  • Type Safety: All components use TypeScript interfaces
  • Performance: Memoization with React.memo and useMemo

Project Structure

App/
├── src/
│   ├── components/          # Reusable UI components
│   ├── pages/              # Screen components
│   ├── libs/
│   │   ├── actions/        # Business logic and API calls
│   │   ├── API/            # API client and types
│   │   └── Navigation/     # Navigation configuration
│   ├── styles/             # Theme and styling
│   ├── types/              # TypeScript type definitions
│   ├── ONYXKEYS.ts         # Onyx state keys
│   ├── ROUTES.ts           # Route definitions
│   ├── SCREENS.ts          # Screen name constants
│   ├── App.tsx             # Application root with providers
│   └── Expensify.tsx       # Core app initialization
├── assets/                 # Images, fonts, etc.
├── tests/                  # Test suites
└── contributingGuides/     # Development documentation

Application Entry Points

App.tsx

The root component establishes the provider hierarchy:
// Simplified provider structure from src/App.tsx
function App() {
  return (
    <SplashScreenStateContextProvider>
      <InitialURLContextProvider>
        <ThemeProvider>
          <LocaleContextProvider>
            <OnyxListItemProvider>
              <SafeAreaProvider>
                <PopoverContextProvider>
                  <KeyboardProvider>
                    <Expensify />
                  </KeyboardProvider>
                </PopoverContextProvider>
              </SafeAreaProvider>
            </OnyxListItemProvider>
          </LocaleContextProvider>
        </ThemeProvider>
      </InitialURLContextProvider>
    </SplashScreenStateContextProvider>
  );
}

Provider Responsibilities

  1. SplashScreenStateContextProvider: Manages splash screen visibility
  2. InitialURLContextProvider: Handles deep linking and initial routes
  3. ThemeProvider: Manages light/dark theme
  4. LocaleContextProvider: Internationalization (i18n)
  5. OnyxListItemProvider: Connects to Onyx data layer
  6. SafeAreaProvider: Device safe areas for notches/rounded corners
  7. PopoverContextProvider: Global popover state
  8. KeyboardProvider: Keyboard state management

Core Modules

Data Layer (Onyx)

Onyx is the custom state management solution built specifically for offline-first apps:
import Onyx from 'react-native-onyx';
import ONYXKEYS from '@src/ONYXKEYS';

// Subscribe to changes
Onyx.connect({
  key: ONYXKEYS.SESSION,
  callback: (session) => {
    console.log('Session updated:', session);
  },
});

// Update data
Onyx.merge(ONYXKEYS.SESSION, {
  authToken: 'new-token',
});
Key Onyx features:
  • Automatic persistence to AsyncStorage
  • Optimistic updates
  • Batched updates for performance
  • TypeScript support

Action Modules

Business logic lives in src/libs/actions/:
  • App.ts: Application lifecycle
  • IOU.ts: Money requests and expenses
  • Report.ts: Report management
  • Policy/: Workspace operations
  • User.ts: User account operations
  • Session.ts: Authentication
Example action:
// Simplified example from src/libs/actions/IOU.ts
function requestMoney(
  report: OnyxEntry<Report>,
  amount: number,
  currency: string,
  merchant: string,
) {
  const optimisticData: OnyxUpdate[] = [
    {
      onyxMethod: Onyx.METHOD.MERGE,
      key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
      value: optimisticTransaction,
    },
  ];

  const successData: OnyxUpdate[] = [...];
  const failureData: OnyxUpdate[] = [...];

  API.write('RequestMoney', parameters, {
    optimisticData,
    successData,
    failureData,
  });
}

HybridApp Architecture

Important: Mobile builds are created from the Mobile-Expensify repository, which combines OldDot (classic Expensify) and NewDot (New Expensify) into a single app.

Key Integration Points

  • The @expensify/react-native-hybrid-app module manages transitions between OldDot and NewDot
  • Environment variables from Mobile-Expensify take precedence
  • STANDALONE_NEW_DOT environment variable controls build mode
  • Session state is shared between OldDot and NewDot
See HybridApp documentation for details.

Build Modes

Standalone Mode

  • Pure NewDot application (web only)
  • Set via STANDALONE_NEW_DOT=true

HybridApp Mode

  • Combined OldDot + NewDot (mobile apps)
  • Default for iOS and Android
  • Manages transitions between old and new UI

React Native New Architecture

New Expensify uses React Native’s new architecture:
  • Fabric Renderer: New rendering system for better performance
  • TurboModules: Improved native module system
  • Hermes Engine: Optimized JavaScript engine

Performance Optimizations

Code Quality

  • TypeScript Strict Mode: Catch errors at compile time
  • ESLint: Automated code quality checks
  • Prettier: Consistent code formatting

Runtime Performance

  • Memoization: React.memo, useMemo, useCallback
  • FlashList: Virtualized lists for better scrolling
  • Bundle Optimization: Tree shaking and code splitting
See React Native Best Practices for detailed guidance.

Testing Strategy

  • Unit Tests: Jest with React Native Testing Library
  • Performance Tests: Reassure framework for regression detection
  • Type Checking: TypeScript with strict mode
# Run tests
npm run test

# Type checking (fast, for development)
npm run typecheck-tsgo

# Type checking (production gate)
npm run typecheck

Deployment Pipeline

Key GitHub Actions workflows:
  • deploy.yml: Production deployment
  • preDeploy.yml: Staging deployment
  • testBuild.yml: PR test builds
  • test.yml: Unit tests
  • typecheck.yml: TypeScript validation
  • lint.yml: Code quality checks

Mobile-Expensify

  • Contains the legacy OldDot application
  • Source for all mobile builds
  • HybridApp integration layer
  • Path: App/Mobile-Expensify/ (submodule)

expensify-common

  • Shared libraries and utilities
  • Common validation and parsing functions
  • Used across multiple Expensify repositories

Next Steps

React Native

Learn about React Native implementation details

State Management

Deep dive into Onyx state management

Navigation

Understand the navigation system

Offline-First

Explore offline-first architecture

Build docs developers (and LLMs) love