Skip to main content
Join us for React Conf on Oct 7-8. Learn more.
This is unreleased documentation for React Native Next version.
For up-to-date documentation, see the latest version (0.81).
Version: Next

Native Components

If you want to build new React Native Components that wrap around a Host Component like a unique kind of CheckBox on Android, or a UIButton on iOS, you should use a Fabric Native Component.

This guide will show you how to build Fabric Native Components, by implementing a web view component. The steps to doing this are:

  1. Define a JavaScript specification using Flow or TypeScript.
  2. Configure the dependencies management system to generate code from the provided spec and to be auto-linked.
  3. Implement the Native code.
  4. Use the Component in an App.

You're going to need a plain template generated application to use the component:

bash
npx @react-native-community/cli@latest init Demo --install-pods false

Creating a WebView Component

This guide will show you how to create a Web View component. We will be creating the component by using the Android's WebView component, and the iOS WKWebView component.

Let's start by creating the folders structure to hold our component's code:

bash
mkdir-p Demo/{specs,android/app/src/main/java/com/webview}

This gives you the following layout where you'll working:

Demo
├── android/app/src/main/java/com/webview
└── ios
└── specs
  • The android/app/src/main/java/com/webview folder is the folder that will contain our Android code.
  • The ios folder is the folder that will contain our iOS code.
  • The specs folder is the folder that will contain the Codegen's specification file.

1. Define Specification for Codegen

Your specification must be defined in either TypeScript or Flow (see Codegen documentation for more details). This is used by Codegen to generate the C++, Objective-C++ and Java to connect your platform code to the JavaScript runtime that React runs in.

The specification file must be named <MODULE_NAME>NativeComponent.{ts|js} to work with Codegen. The suffix NativeComponent is not only a convention, it is actually used by Codegen to detect a spec file.

Use this specification for our WebView Component:

  • TypeScript
  • Flow
Demo/specs/WebViewNativeComponent.ts
importtype{
CodegenTypes,
HostComponent,
ViewProps,
}from'react-native';
import{codegenNativeComponent}from'react-native';

typeWebViewScriptLoadedEvent={
result:'success'|'error';
};

exportinterfaceNativePropsextendsViewProps{
sourceURL?:string;
onScriptLoaded?: CodegenTypes.BubblingEventHandler<WebViewScriptLoadedEvent>|null;
}

exportdefaultcodegenNativeComponent<NativeProps>(
'CustomWebView',
)as HostComponent<NativeProps>;

This specification is composed of three main parts, excluding the imports:

  • The WebViewScriptLoadedEvent is a supporting data type for the data the event needs to pass from native to JavaScript.
  • The NativeProps is a definition of the props that we can set on the component.
  • The codegenNativeComponent statement allows us to codegenerate the code for the custom component and that defines a name for the component used to match the native implementations.

As with Native Modules, you can have multiple specification files in the specs/ directory. For more information about the types you can use, and the platform types these map to, see the appendix.

2. Configure Codegen to run

The specification is used by the React Native's Codegen tools to generate platform specific interfaces and boilerplate for us. To do this, Codegen needs to know where to find our specification and what to do with it. Update your package.json to include:

json
"start":"react-native start",
"test":"jest"
},
"codegenConfig":{
"name":"AppSpec",
"type":"components",
"jsSrcsDir":"specs",
"android":{
"javaPackageName":"com.webview"
},
"ios":{
"componentProvider":{
"CustomWebView":"RCTWebView"
}
}
},
"dependencies":{

With everything wired up for Codegen, we need to prepare our native code to hook into our generated code.

Note that for iOS, we are declaratively mapping the name of the JS component that is exported by the spec (CustomWebView) with the iOS class that will implement the component natively.

2. Building your Native Code

Now it's time to write the native platform code so that when React requires to render a view, the platform can create the right native view and can render it on screen.

You should work through both the Android and iOS platforms.

note

This guide shows you how to create a Native Component that only works with the New Architecture. If you need to support both the New Architecture and the Legacy Architecture, please refer to our backwards compatibility guide.

  • Android
  • iOS

Now it's time to write some Android platform code to be able to render the web view. The steps you need to follow are:

  • Running Codegen
  • Write the code for the ReactWebView
  • Write the code for the ReactWebViewManager
  • Write the code for the ReactWebViewPackage
  • Register the ReactWebViewPackage in the application

1. Run Codegen through Gradle

Run this once to generate boilerplate that your IDE of choice can use.

Demo/
cd android
./gradlew generateCodegenArtifactsFromSchema

Codegen will generate the ViewManager interface you need to implement and the ViewManager delegate for the web view.

2. Write the ReactWebView

The ReactWebView is the component that wraps the Android native view that React Native will render when using our custom Component.

Create a ReactWebView.java or a ReactWebView.kt file in the android/src/main/java/com/webview folder with this code:

  • Java
  • Kotlin
Demo/android/src/main/java/com/webview/ReactWebView.java
packagecom.webview;

importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.webkit.WebView;
importandroid.webkit.WebViewClient;

importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.bridge.ReactContext;
importcom.facebook.react.uimanager.UIManagerHelper;
importcom.facebook.react.uimanager.events.Event;

publicclassReactWebViewextendsWebView{
publicReactWebView(Context context){
super(context);
configureComponent();
}

publicReactWebView(Context context,AttributeSet attrs){
super(context, attrs);
configureComponent();
}

publicReactWebView(Context context,AttributeSet attrs,int defStyleAttr){
super(context, attrs, defStyleAttr);
configureComponent();
}

privatevoidconfigureComponent(){
this.setLayoutParams(newLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
this.setWebViewClient(newWebViewClient(){
@Override
publicvoidonPageFinished(WebView view,String url){
emitOnScriptLoaded(OnScriptLoadedEventResult.success);
}
});
}

publicvoidemitOnScriptLoaded(OnScriptLoadedEventResult result){
ReactContext reactContext =(ReactContext) context;
int surfaceId =UIManagerHelper.getSurfaceId(reactContext);
EventDispatcher eventDispatcher =UIManagerHelper.getEventDispatcherForReactTag(reactContext,getId());
WritableMap payload =Arguments.createMap();
payload.putString("result", result.name());

OnScriptLoadedEvent event =newOnScriptLoadedEvent(surfaceId,getId(), payload);
if(eventDispatcher !=null){
eventDispatcher.dispatchEvent(event);
}
}

publicenumOnScriptLoadedEventResult{
success,
error
}

privateclassOnScriptLoadedEventextendsEvent<OnScriptLoadedEvent>{
privatefinalWritableMap payload;

OnScriptLoadedEvent(int surfaceId,int viewId,WritableMap payload){
super(surfaceId, viewId);
this.payload = payload;
}

@Override
publicStringgetEventName(){
return"onScriptLoaded";
}

@Override
publicWritableMapgetEventData(){
return payload;
}
}
}

The ReactWebView extends the Android WebView so you can reuse all the properties already defined by the platform with ease.

The class defines the three Android constructors but defers their actual implementation to the private configureComponent function. This function takes care of initializing all the components specific properties: in this case you are setting the layout of the WebView and you are defining the WebClient that you use to customize the behavior of the WebView. In this code, the ReactWebView emits an event when the page finishes loading, by implementing the WebClient's onPageFinished method.

The code then defines a helper function to actually emit an event. To emit an event, you have to:

  • grab a reference to the ReactContext;
  • retrieve the surfaceId of the view that you are presenting;
  • grab a reference to the eventDispatcher associated with the view;
  • build the payload for the event using a WritableMap object;
  • create the event object that you need to send to JavaScript;
  • call the eventDispatcher.dispatchEvent to send the event.

The last part of the file contains the definition of the data types you need to send the event:

  • The OnScriptLoadedEventResult, with the possible outcomes of the OnScriptLoaded event.
  • The actual OnScriptLoadedEvent that needs to extend the React Native's Event class.

3. Write the WebViewManager

The WebViewManager is the class that connects the React Native runtime with the native view.

When React receives the instruction from the app to render a specific component, React uses the registered view manager to create the view and to pass all the required properties.

This is the code of the ReactWebViewManager.

  • Java
  • Kotlin
Demo/android/src/main/java/com/webview/ReactWebViewManager.java
packagecom.webview;

importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.module.annotations.ReactModule;
importcom.facebook.react.uimanager.SimpleViewManager;
importcom.facebook.react.uimanager.ThemedReactContext;
importcom.facebook.react.uimanager.ViewManagerDelegate;
importcom.facebook.react.uimanager.annotations.ReactProp;
importcom.facebook.react.viewmanagers.CustomWebViewManagerInterface;
importcom.facebook.react.viewmanagers.CustomWebViewManagerDelegate;

importjava.util.HashMap;
importjava.util.Map;

@ReactModule(name =ReactWebViewManager.REACT_CLASS)
classReactWebViewManagerextendsSimpleViewManager<ReactWebView>implementsCustomWebViewManagerInterface<ReactWebView>{
privatefinalCustomWebViewManagerDelegate<ReactWebView,ReactWebViewManager> delegate =
newCustomWebViewManagerDelegate<>(this);

@Override
publicViewManagerDelegate<ReactWebView>getDelegate(){
return delegate;
}

@Override
publicStringgetName(){
returnREACT_CLASS;
}

@Override
publicReactWebViewcreateViewInstance(ThemedReactContext context){
returnnewReactWebView(context);
}

@ReactProp(name ="sourceUrl")
@Override
publicvoidsetSourceURL(ReactWebView view,String sourceURL){
if(sourceURL ==null){
view.emitOnScriptLoaded(ReactWebView.OnScriptLoadedEventResult.error);
return;
}
view.loadUrl(sourceURL,newHashMap<>());
}

publicstaticfinalStringREACT_CLASS="CustomWebView";

@Override
publicMap<String,Object>getExportedCustomBubblingEventTypeConstants(){
Map<String,Object> map =newHashMap<>();
Map<String,Object> bubblingMap =newHashMap<>();
bubblingMap.put("phasedRegistrationNames",newHashMap<String,String>(){{
put("bubbled","onScriptLoaded");
put("captured","onScriptLoadedCapture");
}});
map.put("onScriptLoaded", bubblingMap);
return map;
}
}

The ReactWebViewManager extends the SimpleViewManager class from React and implements the CustomWebViewManagerInterface, generated by Codegen.

It holds a reference of the CustomWebViewManagerDelegate, another element generated by Codegen.

It then overrides the getName function, which must return the same name used in the spec's codegenNativeComponent function call.

The createViewInstance function is responsible to instantiate a new ReactWebView.

Then, the ViewManager needs to define how all the React's components props will update the native view. In the example, you need to decide how to handle the sourceURL property that React will set on the WebView.

Finally, if the component can emit an event, you need to map the event name by overriding the getExportedCustomBubblingEventTypeConstants for bubbling events, or the getExportedCustomDirectEventTypeConstants for direct events.

4. Write the ReactWebViewPackage

As you do with Native Modules, Native Components also need to implement the ReactPackage class. This is an object that you can use to register the component in the React Native runtime.

This is the code for the ReactWebViewPackage:

  • Java
  • Kotlin
Demo/android/src/main/java/com/webview/ReactWebViewPackage.java
packagecom.webview;

importcom.facebook.react.BaseReactPackage;
importcom.facebook.react.bridge.NativeModule;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.module.model.ReactModuleInfo;
importcom.facebook.react.module.model.ReactModuleInfoProvider;
importcom.facebook.react.uimanager.ViewManager;

importjava.util.Collections;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;

publicclassReactWebViewPackageextendsBaseReactPackage{
@Override
publicList<ViewManager<?,?>>createViewManagers(ReactApplicationContext reactContext){
returnCollections.singletonList(newReactWebViewManager(reactContext));
}

@Override
publicNativeModulegetModule(String s,ReactApplicationContext reactApplicationContext){
if(ReactWebViewManager.REACT_CLASS.equals(s)){
returnnewReactWebViewManager(reactApplicationContext);
}
returnnull;
}

@Override
publicReactModuleInfoProvidergetReactModuleInfoProvider(){
returnnewReactModuleInfoProvider(){
@Override
publicMap<String,ReactModuleInfo>getReactModuleInfos(){
Map<String,ReactModuleInfo> map =newHashMap<>();
map.put(ReactWebViewManager.REACT_CLASS,newReactModuleInfo(
ReactWebViewManager.REACT_CLASS,// name
ReactWebViewManager.REACT_CLASS,// className
false,// canOverrideExistingModule
false,// needsEagerInit
false,// isCxxModule
true// isTurboModule
));
return map;
}
};
}
}

The ReactWebViewPackage extends the BaseReactPackage and implements all the methods required to properly register our component.

  • the createViewManagers method is the factory method that creates the ViewManager that manage the custom views.
  • the getModule method returns the proper ViewManager depending on the View that React Native needs to render.
  • the getReactModuleInfoProvider provides all the information required when registering the module in the runtime,

5. Register the ReactWebViewPackage in the application

Finally, you need to register the ReactWebViewPackage in the application. We do that by modifying the MainApplication file by adding the ReactWebViewPackage to the list of packages returned by the getPackages function.

Demo/app/src/main/java/com/demo/MainApplication.kt
package com.demo

import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
import com.webview.ReactWebViewPackage

class MainApplication :Application(), ReactApplication {

overrideval reactNativeHost: ReactNativeHost =
object:DefaultReactNativeHost(this){
overridefungetPackages(): List<ReactPackage>=
PackageList(this).packages.apply{
add(ReactWebViewPackage())
}

overridefungetJSMainModuleName(): String ="index"

overridefungetUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

overrideval isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
overrideval isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}

overrideval reactHost: ReactHost
get()=getDefaultReactHost(applicationContext, reactNativeHost)

overridefunonCreate(){
super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping)
if(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED){
load()
}
}
}

3. Use your Native Component

Finally, you can use the new component in your app. Update your generated App.tsx to:

Demo/App.tsx
importReactfrom'react';
import{Alert,StyleSheet,View}from'react-native';
importWebViewfrom'./specs/WebViewNativeComponent';

functionApp():React.JSX.Element{
return(
<View style={styles.container}>
<WebView
sourceURL="https://react.dev/"
style={styles.webview}
onScriptLoaded={()=>{
Alert.alert('Page Loaded');
}}
/>
</View>
);
}

const styles =StyleSheet.create({
container:{
flex:1,
alignItems:'center',
alignContent:'center',
},
webview:{
width:'100%',
height:'100%',
},
});

exportdefaultApp;

This code creates an app that uses the new WebView component we created to load the react.dev website.

The app also shows an alert when the web page is loaded.

4. Run your App using the WebView Component

  • Android
  • iOS
bash
yarn run android
AndroidiOS

AltStyle によって変換されたページ (->オリジナル) /