Additional utility functions for application lifecycle management and React development helpers.
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();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>
);
}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;
}
}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>
);
}/**
* 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;
};