DEV Community

Takuya Matsuyama
Takuya Matsuyama

Posted on

How to control the existing React Native view instance from another native module

I've written how to inter-communicate between native modules in a React Native project:

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}
      ...
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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");
  });
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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);
          }
      });
  }
Enter fullscreen mode Exit fullscreen mode

Now you can control the existing views from your native module.
Hope that helps!


Subscribe Newsletter

My YouTube channel

Top comments (4)

Collapse
 
denissb profile image
denissb

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 or runJS 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 and RCT_EXPORT_METHOD

Collapse
 
magouyaware profile image
Justin Anderson

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.

Collapse
 
djmrbig profile image
Damon Turcotte • Edited

I suppose it's a bit late for this comment, but the javascript being injected in this example by @craftzdog is specifically for the purpose of allowing you to quickly roll out your own proof of concept demo using his code, providing immediate feedback from the webview so that you're able to verify that your native code has successfully retrieved a webview instance from native code that's entirely external to the webview, manipulating one native module from another.

The use case he wrote this code for in the first place is actually quite interesting. Injecting multimedia bytecode directly into the webview, entirely bypassing the data transfer bottleneck that exists when writing the data to a JSON string > injecting JSON as browser message > parsing the data for render.

If I remember correctly, his code grabs the file path and id of a media file on the React Native side and hands that to WebView.props.process, which invokes a method in a custom native module which uses the logic seen here in this article to retrieve the webview instance, then appends the file path and file id to the platform-specific absolute path for media files, and writes the bytecode from the file directly into the webview's DOM, providing true native performance for media rendering/playback.

Collapse
 
djmrbig profile image
Damon Turcotte

And since the method outlined in the article is able to retrieve the webview from only the ReactTag, this means that ANY React Native component can be retrieved by simply using a different native tag.

This means that any native component can be made to directly interact with any other native component at any time quite easily, only using React Native as the trigger for a method, rather than having to pass data to and from the javascript layer.