The Expo DOM component system enables React Native applications to render DOM components within WebView contexts. This system provides a bridge between React Native and web technologies, allowing developers to create hybrid components that can access DOM APIs while integrating seamlessly with React Native applications.
Register a React component as a DOM component that runs within a WebView context.
/**
* Registers a React component to run as a DOM component within a WebView
* @param AppModule - React component to register as the DOM component root
*/
function registerDOMComponent(AppModule: any): void;Create imperative handles for DOM components that can be called from the React Native side.
/**
* React hook for creating imperative handles in DOM components
* @param ref - React ref to bind the handle to
* @param init - Function that creates the imperative handle object
* @param deps - Dependency array for recreating the handle
*/
function useDOMImperativeHandle<T extends DOMImperativeFactory>(
ref: Ref<T>,
init: () => T,
deps?: DependencyList
): void;Utility to detect if code is running in a DOM component context.
/**
* Constant indicating if the current environment is a DOM component
*/
const IS_DOM: boolean;/**
* Interface for imperative handle factories in DOM components
*/
interface DOMImperativeFactory {
[key: string]: (...args: JSONValue[]) => void;
}
/**
* Props interface for DOM components with WebView configuration
*/
interface DOMProps extends Omit<RNWebViewProps, 'source'> {
/** Whether to resize the native WebView size based on the DOM content size */
matchContents?: boolean;
/** Whether to use @expo/dom-webview as the underlying WebView implementation */
useExpoDOMWebView?: boolean;
}
/**
* Supported JSON value types for DOM component communication
*/
type JSONValue = boolean | number | string | null | JSONArray | JSONObject;
interface JSONArray extends Array<JSONValue> {}
interface JSONObject {
[key: string]: JSONValue | undefined;
}
/**
* Message structure for communication between React Native and DOM components
*/
type BridgeMessage<TData extends JSONValue> = {
type: string;
data: TData;
};import React from 'react';
import { registerDOMComponent, useDOMImperativeHandle } from 'expo/dom';
interface CanvasComponentProps {
width: number;
height: number;
backgroundColor?: string;
}
interface CanvasHandle {
drawCircle: (x: number, y: number, radius: number, color: string) => void;
clear: () => void;
getImageData: () => string;
}
function CanvasComponent({ width, height, backgroundColor = 'white' }: CanvasComponentProps) {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const handleRef = React.useRef<CanvasHandle>(null);
// Create imperative handle for React Native to call
useDOMImperativeHandle<CanvasHandle>(
handleRef,
() => ({
drawCircle: (x: number, y: number, radius: number, color: string) => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
},
clear: () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, width, height);
},
getImageData: () => {
const canvas = canvasRef.current;
if (!canvas) return '';
return canvas.toDataURL();
}
}),
[width, height, backgroundColor]
);
return (
<canvas
ref={canvasRef}
width={width}
height={height}
style={{ backgroundColor, display: 'block' }}
/>
);
}
// Register the component as a DOM component
registerDOMComponent(CanvasComponent);import { IS_DOM } from 'expo/dom';
function MyComponent() {
if (IS_DOM) {
// Code running in DOM component context
// Has access to DOM APIs, document, window, etc.
return (
<div>
<h1>DOM Component</h1>
<p>This is running in a WebView with full DOM access</p>
</div>
);
} else {
// Code running in React Native context
return (
<View>
<Text>React Native Component</Text>
</View>
);
}
}DOM components communicate with React Native through a message-passing system:
// In DOM component
function sendMessageToReactNative(type: string, data: any) {
// Messages are sent through the WebView bridge
if (typeof window.ReactNativeWebView !== 'undefined') {
window.ReactNativeWebView.postMessage(JSON.stringify({ type, data }));
}
}
// Example: Notify React Native of user interaction
function handleUserClick(userData: any) {
sendMessageToReactNative('user_interaction', {
action: 'click',
data: userData,
timestamp: Date.now(),
});
}// DOM components receive props from React Native
function MyDOMComponent({ initialData, onDataChange }) {
const [data, setData] = useState(initialData);
const handleChange = (newData) => {
setData(newData);
onDataChange?.(newData); // Callback to React Native
};
return (
<div>
{/* Component UI */}
</div>
);
}DOM components run within WebView contexts, which means:
// DOM components should validate all data from React Native
function validateProps(props: any): boolean {
// Implement proper validation
return typeof props === 'object' && props !== null;
}
function SecureDOMComponent(props: any) {
if (!validateProps(props)) {
return <div>Invalid props provided</div>;
}
return (
<div>
{/* Safe to use props */}
</div>
);
}