1import React from 'react'; 2import { findNodeHandle, NativeModules, requireNativeComponent, HostComponent } from 'react-native'; 3 4import { requireNativeModule } from './requireNativeModule'; 5 6// To make the transition from React Native's `requireNativeComponent` to Expo's 7// `requireNativeViewManager` as easy as possible, `requireNativeViewManager` is a drop-in 8// replacement for `requireNativeComponent`. 9// 10// For each view manager, we create a wrapper component that accepts all of the props available to 11// the author of the universal module. This wrapper component splits the props into two sets: props 12// passed to React Native's View (ex: style, testID) and custom view props, which are passed to the 13// adapter view component in a prop called `proxiedProperties`. 14 15/** 16 * A map that caches registered native components. 17 */ 18const nativeComponentsCache = new Map<string, HostComponent<any>>(); 19 20/** 21 * Requires a React Native component from cache if possible. This prevents 22 * "Tried to register two views with the same name" errors on fast refresh, but 23 * also when there are multiple versions of the same package with native component. 24 */ 25function requireCachedNativeComponent<Props>(viewName: string): HostComponent<Props> { 26 const cachedNativeComponent = nativeComponentsCache.get(viewName); 27 28 if (!cachedNativeComponent) { 29 const nativeComponent = requireNativeComponent<Props>(viewName); 30 nativeComponentsCache.set(viewName, nativeComponent); 31 return nativeComponent; 32 } 33 return cachedNativeComponent; 34} 35 36/** 37 * A drop-in replacement for `requireNativeComponent`. 38 */ 39export function requireNativeViewManager<P>(viewName: string): React.ComponentType<P> { 40 const { viewManagersMetadata } = NativeModules.NativeUnimoduleProxy; 41 const viewManagerConfig = viewManagersMetadata?.[viewName]; 42 43 if (__DEV__ && !viewManagerConfig) { 44 const exportedViewManagerNames = Object.keys(viewManagersMetadata).join(', '); 45 console.warn( 46 `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}].` 47 ); 48 } 49 50 // Set up the React Native native component, which is an adapter to the universal module's view 51 // manager 52 const reactNativeViewName = `ViewManagerAdapter_${viewName}`; 53 const ReactNativeComponent = requireCachedNativeComponent(reactNativeViewName); 54 55 class NativeComponent extends React.PureComponent<P> { 56 static displayName = viewName; 57 58 // This will be accessed from native when the prototype functions are called, 59 // in order to find the associated native view. 60 nativeTag: number | null = null; 61 62 componentDidMount(): void { 63 this.nativeTag = findNodeHandle(this); 64 } 65 66 render(): React.ReactNode { 67 return <ReactNativeComponent {...this.props} />; 68 } 69 } 70 71 try { 72 const nativeModule = requireNativeModule(viewName); 73 const nativeViewPrototype = nativeModule.ViewPrototype; 74 75 if (nativeViewPrototype) { 76 // Assign native view functions to the component prototype so they can be accessed from the ref. 77 Object.assign(NativeComponent.prototype, nativeViewPrototype); 78 } 79 } catch { 80 // `requireNativeModule` may throw an error when the native module cannot be found. 81 // In some tests we don't mock the entire modules, but we do want to mock native views. For now, 82 // until we still have to support the legacy modules proxy and don't have better ways to mock, 83 // let's just gracefully skip assigning the prototype functions. 84 // See: https://github.com/expo/expo/blob/main/packages/expo-modules-core/src/__tests__/NativeViewManagerAdapter-test.native.tsx 85 } 86 87 return NativeComponent; 88} 89