I've written how to inter-communicate between native modules in a React Native project:
Inter-communication between native modules on React Native
Takuya Matsuyama ・ Feb 19 '21
Now, it's possible to get a View instance from another native module.
That allows you to control the existing view such as injecting JS into WebView without going through the React Native JS bridge, which is way performant.
Get React View Tag
Every view component has a react tag, which is a view instance identifier managed by RCTUIManager
in iOS and UIManagerModule
in Android.
You can get a view tag in the JS app like so:
import { WebView } from 'react-native-webview'
const YourComponent = (props) => {
const webViewRef = useRef()
useEffect(() => {
const { current: webView } = webViewRef
if (webView) {
console.log(webView.webViewRef.current._nativeTag)
}
}, [])
return (
<WebView
ref={webViewRef}
...
/>
)
}
Well, let's get the view instance from your native module.
iOS
RCTUIManager
provides a method to get a view instance from a react tag:
- (UIView *)viewForReactTag:(NSNumber *)reactTag
Prepare your bridge module to have access to the RCTBridge
by following the steps described in the above post.
In your native bridge, you have to run on the main queue because views should be always controlled on the same thread.
For example, you can get an instance of WebView component by reactTag
like so:
#import <React/RCTUIManager.h>
RCT_EXPORT_METHOD(runJS:(NSString* __nonnull)js
inView:(NSNumber* __nonnull)reactTag
withResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
RCTUnsafeExecuteOnMainQueueSync(^{
RCTUIManager* uiManager = [self.bridge moduleForClass:[RCTUIManager class]];
RNCWebView* webView = (RNCWebView*)[uiManager viewForReactTag:reactTag];
if (webView) {
[webView injectJavaScript:js];
}
resolve(@"OK");
});
}
It runs the given JS code in the webview with the specified tag.
Android
UIManagerModule
provides a method to get a view instance from a react tag:
public View resolveView(int tag)
As described in the above post, you can get an instance of UIManagerModule
from reactContext
.
Then, you can get a view by doing so:
@ReactMethod
public void injectJavaScript(int reactTag, String js) {
UIManagerModule uiManagerModule = this.reactContext.getNativeModule(UIManagerModule.class);
WebView webView = (WebView) uiManagerModule.resolveView(reactTag);
webView.post(new Runnable() {
@Override
public void run() {
webView.evaluateJavascript(js, null);
}
});
}
Now you can control the existing views from your native module.
Hope that helps!
Top comments (2)
I don't understand how this is faster then just getting a ref to the WebView component in React Native and invoking JavaScript via
injectJavaScript
function.If you call
injectJavaScript
orrunJS
from your example the call will still need to go thought the bridge, unless you mean to invoke these methods inside the native code itself, in which case I dont understand the need for@ReactMethod
andRCT_EXPORT_METHOD
You are focusing WAY too much on the example code used here... The specific example of injecting javascript isn't the point of this article at all.
The main point is that you can get a reference to a view from a native module in React Native... This opens up SO MANY possibilities. One of these, which I just tested thanks to this article, is the ability to call a native method using promises, which allows me to write async/await code in JavaScript when calling native methods on my custom native component.
Prior to this article, I only found extremely hacky ways to achieve this, and I didn't want to rely on them. With this mechanism the code is so much cleaner.