Permalink
Browse files

Add props for overriding native component

Summary:
Opening a new PR for #10946 (see discussion there).

This PR builds upon #14775 (iOS ViewManager inheritance) and #14261 (more extensible Android WebView).

**Motivation**
When `WebView.android.js` and `WebView.ios.js` use `requireNativeComponent`, they are hard-coded to require `RCTWebView`. This means if you want to re-use the same JS-logic, but require a custom native WebView-implementation, you have to duplicate the entire JS-code files.

The same is true if you want to pass through any custom events or props, which you want to set on the custom native `WebView`.

What I'm trying to solve with this PR is to able to extend native WebView logic, and being able to re-use and extend existing WebView JS-logic.

This is done by adding a new `nativeConfig` prop on WebView. I've also moved the  extra `requireNativeComponent` config to `WebView.extraNativeComponentConfig` for easier re-use.

**Test plan**
jacobp100 has been kind enough to help me with docs for this new feature. So that is part of the PR and can be read for some information.

I've also created an example app which demonstrates how to use this functionality: https://github.com/cbrevik/webview-native-config-example

If you've implemented the native side as in the example repo above, it should be fairly easy to use from JavaScript like this:
```javascript
import React, { Component, PropTypes } from 'react';
import { WebView, requireNativeComponent, NativeModules } from 'react-native';
const { CustomWebViewManager } = NativeModules;

export default class CustomWebView extends Component {
  static propTypes = {
    ...WebView.propTypes,
    finalUrl: PropTypes.string,
    onNavigationCompleted: PropTypes.func,
  };

  _onNavigationCompleted = (event) => {
    const { onNavigationCompleted } = this.props;
    onNavigationCompleted && onNavigationCompleted(event);
  }

  render() {
    return (
      <WebView
        {...this.props}
        nativeConfig={{
          component: RCTCustomWebView,
          props: {
            finalUrl: this.props.finalUrl,
            onNavigationCompleted: this._onNavigationCompleted,
          },
          viewManager: CustomWebViewManager
        }}
      />
    );
  }
}

const RCTCustomWebView = requireNativeComponent(
  'RCTCustomWebView',
  CustomWebView,
  WebView.extraNativeComponentConfig
);
```

As you see, you require the custom native implementation at the bottom, and send in that along with any custom props with the `nativeConfig` prop on the `WebView`. You also send in the `viewManager` since iOS requires that for `startLoadWithResult`.

**Discussion**
As noted in the original PR, this could in principle be done with more React Native components, to make it easier for the community to re-use and extend native components.
Closes #15016

Differential Revision: D5701280

Pulled By: hramos

fbshipit-source-id: 6c3702654339b037ee81d190c623b8857550e972
  • Loading branch information...
cbrevik authored and facebook-github-bot committed Sep 19, 2017
1 parent ed77dbb commit f426a83d1bcc33e2e920bf855aee190fceaa1d12
@@ -45,6 +45,14 @@ var defaultRenderLoading = () => (
* Renders a native WebView.
*/
class WebView extends React.Component {
static get extraNativeComponentConfig() {
return {
nativeOnly: {
messagingEnabled: PropTypes.bool,
},
};
}
static propTypes = {
...ViewPropTypes,
renderError: PropTypes.func,
@@ -197,6 +205,26 @@ class WebView extends React.Component {
saveFormDataDisabled: PropTypes.bool,
/**
* Override the native component used to render the WebView. Enables a custom native
* WebView which uses the same JavaScript as the original WebView.
*/
nativeConfig: PropTypes.shape({
/*
* The native component used to render the WebView.
*/
component: PropTypes.any,
/*
* Set props directly on the native component WebView. Enables custom props which the
* original WebView doesn't pass through.
*/
props: PropTypes.object,
/*
* Set the ViewManager to use for communcation with the native side.
* @platform ios
*/
viewManager: PropTypes.object,
}),
/*
* Used on Android only, controls whether the given list of URL prefixes should
* make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
* default activity intent for those URL instead of loading it within the webview.
@@ -260,8 +288,12 @@ class WebView extends React.Component {
console.warn('WebView: `source.body` is not supported when using GET.');
}
const nativeConfig = this.props.nativeConfig || {};
let NativeWebView = nativeConfig.component || RCTWebView;
var webView =
<RCTWebView
<NativeWebView
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
@@ -286,6 +318,7 @@ class WebView extends React.Component {
mixedContentMode={this.props.mixedContentMode}
saveFormDataDisabled={this.props.saveFormDataDisabled}
urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
{...nativeConfig.props}
/>;
return (
@@ -402,11 +435,7 @@ class WebView extends React.Component {
}
}
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
nativeOnly: {
messagingEnabled: PropTypes.bool,
},
});
var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig);
var styles = StyleSheet.create({
container: {
@@ -116,6 +116,17 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => (
class WebView extends React.Component {
static JSNavigationScheme = JSNavigationScheme;
static NavigationType = NavigationType;
static get extraNativeComponentConfig() {
return {
nativeOnly: {
onLoadingStart: true,
onLoadingError: true,
onLoadingFinish: true,
onMessage: true,
messagingEnabled: PropTypes.bool,
},
};
}
static propTypes = {
...ViewPropTypes,
@@ -257,7 +268,7 @@ class WebView extends React.Component {
style: ViewPropTypes.style,
/**
* Determines the types of data converted to clickable URLs in the web views content.
* Determines the types of data converted to clickable URLs in the web view's content.
* By default only phone numbers are detected.
*
* You can provide one type or an array of many types.
@@ -365,6 +376,27 @@ class WebView extends React.Component {
'always',
'compatibility'
]),
/**
* Override the native component used to render the WebView. Enables a custom native
* WebView which uses the same JavaScript as the original WebView.
*/
nativeConfig: PropTypes.shape({
/*
* The native component used to render the WebView.
*/
component: PropTypes.any,
/*
* Set props directly on the native component WebView. Enables custom props which the
* original WebView doesn't pass through.
*/
props: PropTypes.object,
/*
* Set the ViewManager to use for communcation with the native side.
* @platform ios
*/
viewManager: PropTypes.object,
}),
};
static defaultProps = {
@@ -412,10 +444,14 @@ class WebView extends React.Component {
webViewStyles.push(styles.hidden);
}
const nativeConfig = this.props.nativeConfig || {};
const viewManager = nativeConfig.viewManager || RCTWebViewManager;
var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => {
var shouldStart = this.props.onShouldStartLoadWithRequest &&
this.props.onShouldStartLoadWithRequest(event.nativeEvent);
RCTWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
viewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
});
var decelerationRate = processDecelerationRate(this.props.decelerationRate);
@@ -429,8 +465,10 @@ class WebView extends React.Component {
const messagingEnabled = typeof this.props.onMessage === 'function';
const NativeWebView = nativeConfig.component || RCTWebView;
var webView =
<RCTWebView
<NativeWebView
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
@@ -451,6 +489,7 @@ class WebView extends React.Component {
allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback}
mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
dataDetectorTypes={this.props.dataDetectorTypes}
{...nativeConfig.props}
/>;
return (
@@ -590,15 +629,7 @@ class WebView extends React.Component {
}
}
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
nativeOnly: {
onLoadingStart: true,
onLoadingError: true,
onLoadingFinish: true,
onMessage: true,
messagingEnabled: PropTypes.bool,
},
});
var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig);
var styles = StyleSheet.create({
container: {
Oops, something went wrong.

0 comments on commit f426a83

Please sign in to comment.