1import React from 'react'; 2import { NativeModules, requireNativeComponent, HostComponent } from 'react-native'; 3 4// To make the transition from React Native's `requireNativeComponent` to Expo's 5// `requireNativeViewManager` as easy as possible, `requireNativeViewManager` is a drop-in 6// replacement for `requireNativeComponent`. 7// 8// For each view manager, we create a wrapper component that accepts all of the props available to 9// the author of the universal module. This wrapper component splits the props into two sets: props 10// passed to React Native's View (ex: style, testID) and custom view props, which are passed to the 11// adapter view component in a prop called `proxiedProperties`. 12 13type NativeExpoComponentProps = { 14 proxiedProperties: object; 15}; 16 17/** 18 * A map that caches registered native components. 19 */ 20const nativeComponentsCache = new Map<string, HostComponent<any>>(); 21 22/** 23 * Requires a React Native component from cache if possible. This prevents 24 * "Tried to register two views with the same name" errors on fast refresh, but 25 * also when there are multiple versions of the same package with native component. 26 */ 27function requireCachedNativeComponent<Props>(viewName: string): HostComponent<Props> { 28 const cachedNativeComponent = nativeComponentsCache.get(viewName); 29 30 if (!cachedNativeComponent) { 31 const nativeComponent = requireNativeComponent<Props>(viewName); 32 nativeComponentsCache.set(viewName, nativeComponent); 33 return nativeComponent; 34 } 35 return cachedNativeComponent; 36} 37 38/** 39 * A drop-in replacement for `requireNativeComponent`. 40 */ 41export function requireNativeViewManager<P>(viewName: string): React.ComponentType<P> { 42 const { viewManagersMetadata } = NativeModules.NativeUnimoduleProxy; 43 const viewManagerConfig = viewManagersMetadata?.[viewName]; 44 45 if (__DEV__ && !viewManagerConfig) { 46 const exportedViewManagerNames = Object.keys(viewManagersMetadata).join(', '); 47 console.warn( 48 `The native view manager required by name (${viewName}) from NativeViewManagerAdapter isn't exported by expo-modules-core. Views of this type may not render correctly. Exported view managers: [${exportedViewManagerNames}].` 49 ); 50 } 51 52 // Set up the React Native native component, which is an adapter to the universal module's view 53 // manager 54 const reactNativeViewName = `ViewManagerAdapter_${viewName}`; 55 const ReactNativeComponent = 56 requireCachedNativeComponent<NativeExpoComponentProps>(reactNativeViewName); 57 const proxiedPropsNames = viewManagerConfig?.propsNames ?? []; 58 59 // Define a component for universal-module authors to access their native view manager 60 const NativeComponentAdapter = React.forwardRef<any>((props, ref) => { 61 const nativeProps = omit(props, proxiedPropsNames); 62 const proxiedProps = pick(props, proxiedPropsNames); 63 return <ReactNativeComponent {...nativeProps} proxiedProperties={proxiedProps} ref={ref} />; 64 }) as React.ComponentType<P>; 65 NativeComponentAdapter.displayName = `Adapter<${viewName}>`; 66 return NativeComponentAdapter; 67} 68 69function omit(props: Record<string, any>, propNames: string[]) { 70 const copied = { ...props }; 71 for (const propName of propNames) { 72 delete copied[propName]; 73 } 74 return copied; 75} 76 77function pick(props: Record<string, any>, propNames: string[]) { 78 return propNames.reduce((prev, curr) => { 79 if (curr in props) { 80 prev[curr] = props[curr]; 81 } 82 return prev; 83 }, {}); 84} 85