1import React from 'react';
2import { NativeModules, requireNativeComponent } 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 drop-in replacement for `requireNativeComponent`.
19 */
20export function requireNativeViewManager<P = any>(viewName: string): React.ComponentType<P> {
21  const { viewManagersMetadata } = NativeModules.NativeUnimoduleProxy;
22  const viewManagerConfig = viewManagersMetadata?.[viewName];
23
24  if (__DEV__ && !viewManagerConfig) {
25    const exportedViewManagerNames = Object.keys(viewManagersMetadata).join(', ');
26    console.warn(
27      `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}].`
28    );
29  }
30
31  // Set up the React Native native component, which is an adapter to the universal module's view
32  // manager
33  const reactNativeViewName = `ViewManagerAdapter_${viewName}`;
34  const ReactNativeComponent =
35    requireNativeComponent<NativeExpoComponentProps>(reactNativeViewName);
36  const proxiedPropsNames = viewManagerConfig?.propsNames ?? [];
37
38  // Define a component for universal-module authors to access their native view manager
39  function NativeComponentAdapter(props, ref) {
40    const nativeProps = omit(props, proxiedPropsNames);
41    const proxiedProps = pick(props, proxiedPropsNames);
42    return <ReactNativeComponent {...nativeProps} proxiedProperties={proxiedProps} ref={ref} />;
43  }
44  NativeComponentAdapter.displayName = `Adapter<${viewName}>`;
45  return React.forwardRef(NativeComponentAdapter);
46}
47
48function omit(props: Record<string, any>, propNames: string[]) {
49  const copied = { ...props };
50  for (const propName of propNames) {
51    delete copied[propName];
52  }
53  return copied;
54}
55
56function pick(props: Record<string, any>, propNames: string[]) {
57  return propNames.reduce((prev, curr) => {
58    if (curr in props) {
59      prev[curr] = props[curr];
60    }
61    return prev;
62  }, {});
63}
64