Integration system for React Native components backed by native view managers, with automatic prototype binding and lifecycle management.
Function for creating React Native components from Expo native view managers with automatic prototype binding.
/**
* A drop-in replacement for requireNativeComponent that works with Expo modules
* Creates React Native components backed by native view managers
* @param moduleName - Name of the native module containing the view manager
* @param viewName - Optional specific view name within the module
* @returns React component type that can be used in JSX
*/
function requireNativeViewManager<P>(
moduleName: string,
viewName?: string
): ComponentType<P>;Usage Examples:
import { requireNativeViewManager } from "expo-modules-core";
import { ViewProps } from "react-native";
// Basic native view component
interface MapViewProps extends ViewProps {
region: {
latitude: number;
longitude: number;
latitudeDelta: number;
longitudeDelta: number;
};
onRegionChange?: (region: any) => void;
}
// Create native view component
const MapView = requireNativeViewManager<MapViewProps>("ExpoMapView");
// Use in component
function MapScreen() {
const [region, setRegion] = useState({
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
});
return (
<MapView
style={{ flex: 1 }}
region={region}
onRegionChange={setRegion}
/>
);
}
// Multiple views from same module
const CameraView = requireNativeViewManager<CameraViewProps>("ExpoCamera", "CameraView");
const CameraPreview = requireNativeViewManager<CameraPreviewProps>("ExpoCamera", "PreviewView");
function CameraScreen() {
return (
<div>
<CameraView style={{ flex: 1 }} />
<CameraPreview style={{ height: 100 }} />
</div>
);
}The view manager system automatically binds native methods to component instances through prototypes.
import { requireNativeViewManager } from "expo-modules-core";
import { useRef } from "react";
// Native view with callable methods
interface VideoViewProps extends ViewProps {
source: { uri: string };
onStatusUpdate?: (status: VideoStatus) => void;
}
// Methods automatically bound from native view prototype
interface VideoViewMethods {
playAsync(): Promise<void>;
pauseAsync(): Promise<void>;
seekToAsync(positionMillis: number): Promise<void>;
getStatusAsync(): Promise<VideoStatus>;
}
const VideoView = requireNativeViewManager<VideoViewProps>("ExpoVideo");
function VideoPlayer({ source }: { source: { uri: string } }) {
// Ref will have native methods available
const videoRef = useRef<VideoViewMethods>(null);
const handlePlay = async () => {
if (videoRef.current) {
await videoRef.current.playAsync();
}
};
const handlePause = async () => {
if (videoRef.current) {
await videoRef.current.pauseAsync();
}
};
const handleSeek = async (position: number) => {
if (videoRef.current) {
await videoRef.current.seekToAsync(position);
}
};
return (
<div>
<VideoView
ref={videoRef}
source={source}
style={{ width: 300, height: 200 }}
onStatusUpdate={(status) => console.log("Video status:", status)}
/>
<div>
<button onClick={handlePlay}>Play</button>
<button onClick={handlePause}>Pause</button>
<button onClick={() => handleSeek(30000)}>Seek to 30s</button>
</div>
</div>
);
}Native views use static view configurations for React Native integration with proper event handling.
// Example view config structure (handled internally)
interface ViewConfig {
validAttributes: Record<string, any>;
directEventTypes: Record<string, { registrationName: string }>;
}
// Internal view registration process
function internalViewRegistration(moduleName: string, viewName?: string) {
// Get view config from native module
const expoViewConfig = globalThis.expo?.getViewConfig(moduleName, viewName);
// Create React Native component with proper configuration
return {
uiViewClassName: `ViewManagerAdapter_${moduleName}_${viewName || 'default'}`,
validAttributes: expoViewConfig?.validAttributes || {},
directEventTypes: expoViewConfig?.directEventTypes || {},
};
}Usage Examples:
import { requireNativeViewManager } from "expo-modules-core";
// View with custom events
interface CustomViewProps extends ViewProps {
customProperty: string;
onCustomEvent?: (event: { nativeEvent: { data: string } }) => void;
onAnotherEvent?: (event: { nativeEvent: { value: number } }) => void;
}
const CustomView = requireNativeViewManager<CustomViewProps>("MyCustomModule");
function CustomComponent() {
return (
<CustomView
customProperty="test"
onCustomEvent={(event) => {
console.log("Custom event:", event.nativeEvent.data);
}}
onAnotherEvent={(event) => {
console.log("Another event:", event.nativeEvent.value);
}}
style={{ width: 200, height: 100 }}
/>
);
}Best practices for handling native view loading errors and providing fallbacks.
import { requireNativeViewManager } from "expo-modules-core";
import { Platform } from "expo-modules-core";
// Conditional view loading with fallbacks
function createNativeView<T>(moduleName: string, fallbackComponent?: ComponentType<T>) {
try {
return requireNativeViewManager<T>(moduleName);
} catch (error) {
console.warn(`Native view ${moduleName} not available:`, error);
return fallbackComponent || (() => <div>Native view not available</div>);
}
}
// Platform-specific views
const PlatformSpecificView = Platform.select({
native: () => requireNativeViewManager<ViewProps>("NativeOnlyView"),
web: () => ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
default: () => () => <div>Unsupported platform</div>,
})();
// Graceful degradation
interface MediaPlayerProps extends ViewProps {
source: { uri: string };
controls?: boolean;
}
const NativeMediaPlayer = (() => {
try {
return requireNativeViewManager<MediaPlayerProps>("ExpoMediaPlayer");
} catch {
// Fallback to HTML5 video on web
return ({ source, style, ...props }: MediaPlayerProps) => (
<video
src={source.uri}
controls
style={style}
{...props}
/>
);
}
})();
function MediaComponent({ source }: { source: { uri: string } }) {
return (
<NativeMediaPlayer
source={source}
controls={true}
style={{ width: '100%', height: 200 }}
/>
);
}Complex usage patterns for native view integration.
import { requireNativeViewManager } from "expo-modules-core";
import { forwardRef, useImperativeHandle, useRef } from "react";
// Forwarding refs with native methods
interface WebViewProps extends ViewProps {
source: { uri: string };
onLoadEnd?: () => void;
}
interface WebViewMethods {
goBack(): void;
goForward(): void;
reload(): void;
evaluateJavaScript(script: string): Promise<string>;
}
const NativeWebView = requireNativeViewManager<WebViewProps>("ExpoWebView");
const WebView = forwardRef<WebViewMethods, WebViewProps>((props, ref) => {
const nativeRef = useRef<WebViewMethods>(null);
useImperativeHandle(ref, () => ({
goBack: () => nativeRef.current?.goBack(),
goForward: () => nativeRef.current?.goForward(),
reload: () => nativeRef.current?.reload(),
evaluateJavaScript: (script: string) =>
nativeRef.current?.evaluateJavaScript(script) || Promise.resolve(''),
}));
return <NativeWebView ref={nativeRef} {...props} />;
});
// Usage with ref
function WebBrowser() {
const webViewRef = useRef<WebViewMethods>(null);
const handleGoBack = () => {
webViewRef.current?.goBack();
};
const handleRunScript = async () => {
const result = await webViewRef.current?.evaluateJavaScript('document.title');
console.log('Page title:', result);
};
return (
<div>
<WebView
ref={webViewRef}
source={{ uri: 'https://example.com' }}
style={{ flex: 1 }}
/>
<div>
<button onClick={handleGoBack}>Back</button>
<button onClick={handleRunScript}>Get Title</button>
</div>
</div>
);
}
// Component composition with multiple native views
function ComplexMediaView() {
const audioRef = useRef(null);
const videoRef = useRef(null);
const AudioPlayer = requireNativeViewManager("ExpoAudio");
const VideoPlayer = requireNativeViewManager("ExpoVideo");
const Visualizer = requireNativeViewManager("ExpoAudioVisualizer");
return (
<div style={{ flex: 1 }}>
<VideoPlayer
ref={videoRef}
style={{ flex: 1 }}
source={{ uri: 'video.mp4' }}
/>
<div style={{ flexDirection: 'row' }}>
<AudioPlayer
ref={audioRef}
style={{ flex: 1, height: 60 }}
source={{ uri: 'audio.mp3' }}
/>
<Visualizer
style={{ flex: 1, height: 60 }}
audioSource={audioRef}
/>
</div>
</div>
);
}/**
* React component type that can be used in JSX
*/
type ComponentType<P = {}> = React.ComponentType<P>;
/**
* Base props that all React Native views inherit
*/
interface ViewProps {
style?: any;
testID?: string;
accessibilityLabel?: string;
[key: string]: any;
}
/**
* Native methods interface that can be extended for specific views
*/
interface NativeMethods {
focus?: () => void;
blur?: () => void;
measure?: (callback: (x: number, y: number, width: number, height: number, pageX: number, pageY: number) => void) => void;
}