CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-expo-modules-core

The core infrastructure for Expo Modules architecture enabling seamless integration between React Native applications and native platform code.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

app-utilities.mddocs/

App Utilities

Additional utility functions for application lifecycle management and React development helpers.

Capabilities

App Reload Functionality

Function to programmatically reload the application using the same JavaScript bundle.

/**
 * Reloads the app using the same JavaScript bundle currently running
 * Works for both release and debug builds
 * Unlike Updates.reloadAsync(), does not use new updates
 * @param reason - Optional reason for reloading (used on some platforms)
 * @returns Promise that resolves when reload is initiated
 */
function reloadAppAsync(reason?: string): Promise<void>;

Usage Examples:

import { reloadAppAsync } from "expo-modules-core";

// Basic app reload
async function handleAppReload() {
  try {
    await reloadAppAsync("User requested reload");
    // This code may not execute as app is reloading
  } catch (error) {
    console.error("Failed to reload app:", error);
  }
}

// Reload with user confirmation
async function confirmAndReload(reason: string) {
  const shouldReload = confirm(`Reload app? Reason: ${reason}`);
  if (shouldReload) {
    await reloadAppAsync(reason);
  }
}

// Reload after critical error
class ErrorBoundary extends Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error: Error) {
    return { hasError: true };
  }
  
  componentDidCatch(error: Error, errorInfo: any) {
    console.error("Critical error caught:", error, errorInfo);
  }
  
  handleReload = async () => {
    await reloadAppAsync("Recovery from critical error");
  };
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Something went wrong</h2>
          <button onClick={this.handleReload}>
            Reload App
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Scheduled reload (e.g., after configuration changes)
class ConfigurationManager {
  private reloadTimeout?: NodeJS.Timeout;
  
  scheduleReload(delayMs: number, reason: string) {
    if (this.reloadTimeout) {
      clearTimeout(this.reloadTimeout);
    }
    
    this.reloadTimeout = setTimeout(async () => {
      console.log(`Reloading app in response to: ${reason}`);
      await reloadAppAsync(reason);
    }, delayMs);
  }
  
  cancelScheduledReload() {
    if (this.reloadTimeout) {
      clearTimeout(this.reloadTimeout);
      this.reloadTimeout = undefined;
    }
  }
}

// Usage
const configManager = new ConfigurationManager();

// Reload after 5 seconds due to config change
configManager.scheduleReload(5000, "Configuration updated");

// Cancel if user takes action
configManager.cancelScheduledReload();

React Development Helpers

Utilities specifically designed for React development and testing scenarios.

/**
 * Create a React ref object that is friendly for snapshots
 * Represented as [React.ref] in snapshots instead of complex object
 * @returns React ref object for component instances
 */
function createSnapshotFriendlyRef<T>(): RefObject<T | null>;

Usage Examples:

import { createSnapshotFriendlyRef } from "expo-modules-core";
import { useEffect } from "react";

// Component with snapshot-friendly ref
function VideoPlayer({ source }: { source: string }) {
  const videoRef = createSnapshotFriendlyRef<HTMLVideoElement>();
  
  useEffect(() => {
    const video = videoRef.current;
    if (video) {
      video.src = source;
      video.load();
    }
  }, [source]);
  
  const handlePlay = () => {
    videoRef.current?.play();
  };
  
  const handlePause = () => {
    videoRef.current?.pause();
  };
  
  return (
    <div>
      <video ref={videoRef} controls />
      <div>
        <button onClick={handlePlay}>Play</button>
        <button onClick={handlePause}>Pause</button>
      </div>
    </div>
  );
}

// Testing component with snapshot
import { render } from '@testing-library/react';

test('VideoPlayer snapshot', () => {
  const { container } = render(<VideoPlayer source="video.mp4" />);
  
  // The ref will appear as [React.ref] in snapshot instead of complex object
  expect(container).toMatchSnapshot();
});

// Multiple refs in a component
function MediaController() {
  const audioRef = createSnapshotFriendlyRef<HTMLAudioElement>();
  const videoRef = createSnapshotFriendlyRef<HTMLVideoElement>();
  const canvasRef = createSnapshotFriendlyRef<HTMLCanvasElement>();
  
  const syncPlayback = () => {
    const audio = audioRef.current;
    const video = videoRef.current;
    
    if (audio && video) {
      audio.currentTime = video.currentTime;
      audio.play();
      video.play();
    }
  };
  
  const drawVisualization = () => {
    const canvas = canvasRef.current;
    const audio = audioRef.current;
    
    if (canvas && audio) {
      const ctx = canvas.getContext('2d');
      // Draw audio visualization
    }
  };
  
  return (
    <div>
      <video ref={videoRef} />
      <audio ref={audioRef} />
      <canvas ref={canvasRef} />
      <button onClick={syncPlayback}>Sync & Play</button>
      <button onClick={drawVisualization}>Visualize</button>
    </div>
  );
}

Development and Debugging Utilities

Helper functions for development and debugging scenarios.

import { reloadAppAsync, Platform } from "expo-modules-core";

// Development-only reload functionality
class DevTools {
  static isDevMode(): boolean {
    return __DEV__ === true;
  }
  
  static async enableHotReload() {
    if (!this.isDevMode()) {
      console.warn("Hot reload only available in development mode");
      return;
    }
    
    // Setup hot reload triggers
    if (Platform.canUseEventListeners) {
      window.addEventListener('keydown', (event) => {
        if (event.ctrlKey && event.key === 'r') {
          event.preventDefault();
          this.reloadApp("Hot reload shortcut");
        }
      });
    }
  }
  
  static async reloadApp(reason: string) {
    if (this.isDevMode()) {
      console.log(`[DevTools] Reloading: ${reason}`);
      await reloadAppAsync(reason);
    } else {
      console.warn("Reload not allowed in production");
    }
  }
  
  static createErrorHandler() {
    return (error: Error, errorInfo: any) => {
      console.error("[DevTools] Error:", error);
      console.error("[DevTools] Error Info:", errorInfo);
      
      if (this.isDevMode()) {
        // In development, offer to reload
        setTimeout(() => {
          if (confirm("An error occurred. Reload the app?")) {
            this.reloadApp("Error recovery");
          }
        }, 1000);
      }
    };
  }
}

// Usage in development
if (__DEV__) {
  DevTools.enableHotReload();
}

// Error boundary with dev tools integration
class DevErrorBoundary extends Component {
  state = { hasError: false, error: null };
  
  static getDerivedStateFromError(error: Error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error: Error, errorInfo: any) {
    const handleError = DevTools.createErrorHandler();
    handleError(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>Development Error</h2>
          {__DEV__ && (
            <details>
              <summary>Error Details</summary>
              <pre>{this.state.error?.stack}</pre>
            </details>
          )}
          <button onClick={() => DevTools.reloadApp("Manual error recovery")}>
            Reload App
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

Application Lifecycle Patterns

Common patterns for managing application state and lifecycle events.

import { reloadAppAsync, Platform } from "expo-modules-core";

// Application state manager
class AppStateManager {
  private static instance: AppStateManager;
  private listeners: Array<(event: AppEvent) => void> = [];
  
  static getInstance(): AppStateManager {
    if (!this.instance) {
      this.instance = new AppStateManager();
    }
    return this.instance;
  }
  
  onAppEvent(listener: (event: AppEvent) => void) {
    this.listeners.push(listener);
    return () => {
      const index = this.listeners.indexOf(listener);
      if (index >= 0) {
        this.listeners.splice(index, 1);
      }
    };
  }
  
  private notifyListeners(event: AppEvent) {
    this.listeners.forEach(listener => {
      try {
        listener(event);
      } catch (error) {
        console.error("Error in app event listener:", error);
      }
    });
  }
  
  async reloadForUpdate(updateInfo: UpdateInfo) {
    this.notifyListeners({ type: 'reload_starting', updateInfo });
    
    try {
      await reloadAppAsync(`Update available: ${updateInfo.version}`);
    } catch (error) {
      this.notifyListeners({ type: 'reload_failed', error });
    }
  }
  
  async reloadForError(error: Error) {
    this.notifyListeners({ type: 'error_reload', error });
    
    // Wait a bit to let user see the error
    setTimeout(async () => {
      await reloadAppAsync(`Error recovery: ${error.message}`);
    }, 2000);
  }
}

type AppEvent = 
  | { type: 'reload_starting'; updateInfo: UpdateInfo }
  | { type: 'reload_failed'; error: Error }
  | { type: 'error_reload'; error: Error };

type UpdateInfo = {
  version: string;
  description: string;
};

// Usage in app root
function App() {
  const appStateManager = AppStateManager.getInstance();
  
  useEffect(() => {
    const unsubscribe = appStateManager.onAppEvent((event) => {
      switch (event.type) {
        case 'reload_starting':
          // Show loading indicator
          console.log("App reloading for update:", event.updateInfo.version);
          break;
        case 'reload_failed':
          // Show error message
          console.error("Reload failed:", event.error);
          break;
        case 'error_reload':
          // Show error recovery message
          console.log("Reloading due to error:", event.error.message);
          break;
      }
    });
    
    return unsubscribe;
  }, []);
  
  return (
    <DevErrorBoundary>
      <MainApplication />
    </DevErrorBoundary>
  );
}

Types

/**
 * React ref object type for snapshot-friendly refs
 */
interface RefObject<T> {
  readonly current: T | null;
}

/**
 * Application event types for lifecycle management
 */
type AppEvent = {
  type: string;
  [key: string]: any;
};

docs

app-utilities.md

error-handling.md

event-communication.md

index.md

native-modules.md

native-views.md

permissions.md

platform-utilities.md

shared-memory.md

uuid-generation.md

tile.json