1import React from 'react';
2import { NativeModules, UIManager, ViewPropTypes, 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
13// NOTE: React Native is moving away from runtime PropTypes and may remove ViewPropTypes, in which
14// case we will need another way to separate standard React Native view props from other props,
15// which we proxy through the adapter
16const ViewPropTypesKeys = Object.keys(ViewPropTypes);
17
18type NativeExpoComponentProps = {
19  proxiedProperties: object;
20};
21
22/**
23 * A drop-in replacement for `requireNativeComponent`.
24 */
25export function requireNativeViewManager<P = any>(viewName: string): React.ComponentType<P> {
26  if (__DEV__) {
27    const { NativeUnimoduleProxy } = NativeModules;
28    if (!NativeUnimoduleProxy.viewManagersNames.includes(viewName)) {
29      const exportedViewManagerNames = NativeUnimoduleProxy.viewManagersNames.join(', ');
30      console.warn(
31        `The native view manager required by name (${viewName}) from NativeViewManagerAdapter isn't exported by @unimodules/react-native-adapter. Views of this type may not render correctly. Exported view managers: [${exportedViewManagerNames}].`
32      );
33    }
34  }
35
36  // Set up the React Native native component, which is an adapter to the universal module's view
37  // manager
38  const reactNativeViewName = `ViewManagerAdapter_${viewName}`;
39  const ReactNativeComponent = requireNativeComponent<NativeExpoComponentProps>(
40    reactNativeViewName
41  );
42  const reactNativeUIConfiguration = (UIManager.getViewManagerConfig
43    ? UIManager.getViewManagerConfig(reactNativeViewName)
44    : UIManager[reactNativeViewName]) || {
45    NativeProps: {},
46    directEventTypes: {},
47  };
48  const reactNativeComponentPropNames = [
49    'children',
50    ...ViewPropTypesKeys,
51    ...Object.keys(reactNativeUIConfiguration.NativeProps),
52    ...Object.keys(reactNativeUIConfiguration.directEventTypes),
53  ];
54
55  // Define a component for universal-module authors to access their native view manager
56  function NativeComponentAdapter(props, ref) {
57    const nativeProps = pick(props, reactNativeComponentPropNames);
58    const proxiedProps = omit(props, reactNativeComponentPropNames);
59    return <ReactNativeComponent {...nativeProps} proxiedProperties={proxiedProps} ref={ref} />;
60  }
61  NativeComponentAdapter.displayName = `Adapter<${viewName}>`;
62  return React.forwardRef(NativeComponentAdapter);
63}
64
65function omit(props: Record<string, any>, propNames: string[]) {
66  const copied = { ...props };
67  for (const propName of propNames) {
68    delete copied[propName];
69  }
70  return copied;
71}
72
73function pick(props: Record<string, any>, propNames: string[]) {
74  return propNames.reduce((prev, curr) => {
75    if (curr in props) {
76      prev[curr] = props[curr];
77    }
78    return prev;
79  }, {});
80}
81