Skip to content

Instantly share code, notes, and snippets.

@Krisztiaan
Created October 1, 2021 18:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Krisztiaan/9ea6f8214c61a2987fd476350985b9f3 to your computer and use it in GitHub Desktop.
Save Krisztiaan/9ea6f8214c61a2987fd476350985b9f3 to your computer and use it in GitHub Desktop.
import React, { useRef, useImperativeHandle } from 'react';
import type { HostComponent } from 'react-native';
import {
NativeModules,
findNodeHandle,
Platform,
UIManager,
requireNativeComponent,
} from 'react-native';
import invariant from 'invariant';
const nativeComponentCache: Record<string, HostComponent<any>> = {};
export type NativeComponent<Props, Commands> = HostComponent<Props> & Commands;
export type NativeComponentRef<Props, Commands> = Commands & React.Component<Props, any, any>;
export default function getNativeComponent<
Props,
Commands extends { [key: string]: (...args: any[]) => any } = Record<string, never>,
>(componentName: string, viewManagerName?: string) {
try {
nativeComponentCache[componentName] = requireNativeComponent<Props>(componentName);
} catch (error) {
if (
!(
error instanceof Error &&
error.name === 'Invariant Violation' &&
nativeComponentCache[componentName]
)
)
throw error;
}
const nativeComponent = nativeComponentCache[componentName];
invariant(nativeComponent, `Unable to find "${componentName}" in the View registry`);
const nativeViewManager = NativeModules[viewManagerName || `${componentName}Manager`] as {
[key in keyof Commands]: (
handle: number,
...args: Parameters<Commands[key]>
) => ReturnType<Commands[key]>;
};
invariant(
!viewManagerName?.length || nativeViewManager,
`Unable to find "${viewManagerName ?? ''}" in the ViewManager registry`,
);
function runCommand<TKey extends keyof Commands>(
componentOrHandle: null | number | React.Component<any, any> | React.ComponentClass<any>,
commandName: TKey,
args: Parameters<Commands[TKey]>,
) {
const handle = findNodeHandle(componentOrHandle);
if (!handle) return null;
const ConfigCommands = UIManager.getViewManagerConfig(componentName).Commands as Record<
keyof Commands,
string | number | undefined
>;
const getCommandId = (command: keyof Commands) => {
const commandId: string | number | undefined = ConfigCommands[command];
invariant(commandId, `Command "${command}" is not implemented by ${componentName}`);
return commandId;
};
return Platform.select({
android: () => UIManager.dispatchViewManagerCommand(handle, getCommandId(commandName), args),
windows: () => UIManager.dispatchViewManagerCommand(handle, getCommandId(commandName), args),
ios: () => nativeViewManager[commandName](handle, ...args),
macos: () => nativeViewManager[commandName](handle, ...args),
default: () => {
throw Error(`Platform ${Platform.OS} not supported by \`getSafeNativeComponent\`.`);
},
})();
}
return React.forwardRef<Commands & React.Component<Props>, Props>((props, ref) => {
const nativeRef = useRef<HostComponent<Props>>();
useImperativeHandle(ref, () => {
return new Proxy({} as any, {
get(_t, pName: string) {
return (...args: any) => {
invariant(nativeRef.current, 'Native ref was not set.');
return runCommand(nativeRef.current, pName, args);
};
},
});
});
return React.createElement(nativeComponent, {
...props,
ref: nativeRef,
});
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment