Skip to content

Commit f426a83

Browse files
cbrevikfacebook-github-bot
authored andcommitted
Add props for overriding native component
Summary: Opening a new PR for facebook#10946 (see discussion there). This PR builds upon facebook#14775 (iOS ViewManager inheritance) and facebook#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 facebook#15016 Differential Revision: D5701280 Pulled By: hramos fbshipit-source-id: 6c3702654339b037ee81d190c623b8857550e972
1 parent ed77dbb commit f426a83

File tree

8 files changed

+587
-22
lines changed

8 files changed

+587
-22
lines changed

Libraries/Components/WebView/WebView.android.js

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ var defaultRenderLoading = () => (
4545
* Renders a native WebView.
4646
*/
4747
class WebView extends React.Component {
48+
static get extraNativeComponentConfig() {
49+
return {
50+
nativeOnly: {
51+
messagingEnabled: PropTypes.bool,
52+
},
53+
};
54+
}
55+
4856
static propTypes = {
4957
...ViewPropTypes,
5058
renderError: PropTypes.func,
@@ -197,6 +205,26 @@ class WebView extends React.Component {
197205
saveFormDataDisabled: PropTypes.bool,
198206

199207
/**
208+
* Override the native component used to render the WebView. Enables a custom native
209+
* WebView which uses the same JavaScript as the original WebView.
210+
*/
211+
nativeConfig: PropTypes.shape({
212+
/*
213+
* The native component used to render the WebView.
214+
*/
215+
component: PropTypes.any,
216+
/*
217+
* Set props directly on the native component WebView. Enables custom props which the
218+
* original WebView doesn't pass through.
219+
*/
220+
props: PropTypes.object,
221+
/*
222+
* Set the ViewManager to use for communcation with the native side.
223+
* @platform ios
224+
*/
225+
viewManager: PropTypes.object,
226+
}),
227+
/*
200228
* Used on Android only, controls whether the given list of URL prefixes should
201229
* make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
202230
* default activity intent for those URL instead of loading it within the webview.
@@ -260,8 +288,12 @@ class WebView extends React.Component {
260288
console.warn('WebView: `source.body` is not supported when using GET.');
261289
}
262290

291+
const nativeConfig = this.props.nativeConfig || {};
292+
293+
let NativeWebView = nativeConfig.component || RCTWebView;
294+
263295
var webView =
264-
<RCTWebView
296+
<NativeWebView
265297
ref={RCT_WEBVIEW_REF}
266298
key="webViewKey"
267299
style={webViewStyles}
@@ -286,6 +318,7 @@ class WebView extends React.Component {
286318
mixedContentMode={this.props.mixedContentMode}
287319
saveFormDataDisabled={this.props.saveFormDataDisabled}
288320
urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
321+
{...nativeConfig.props}
289322
/>;
290323

291324
return (
@@ -402,11 +435,7 @@ class WebView extends React.Component {
402435
}
403436
}
404437

405-
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
406-
nativeOnly: {
407-
messagingEnabled: PropTypes.bool,
408-
},
409-
});
438+
var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig);
410439

411440
var styles = StyleSheet.create({
412441
container: {

Libraries/Components/WebView/WebView.ios.js

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => (
116116
class WebView extends React.Component {
117117
static JSNavigationScheme = JSNavigationScheme;
118118
static NavigationType = NavigationType;
119+
static get extraNativeComponentConfig() {
120+
return {
121+
nativeOnly: {
122+
onLoadingStart: true,
123+
onLoadingError: true,
124+
onLoadingFinish: true,
125+
onMessage: true,
126+
messagingEnabled: PropTypes.bool,
127+
},
128+
};
129+
}
119130

120131
static propTypes = {
121132
...ViewPropTypes,
@@ -257,7 +268,7 @@ class WebView extends React.Component {
257268
style: ViewPropTypes.style,
258269

259270
/**
260-
* Determines the types of data converted to clickable URLs in the web views content.
271+
* Determines the types of data converted to clickable URLs in the web view's content.
261272
* By default only phone numbers are detected.
262273
*
263274
* You can provide one type or an array of many types.
@@ -365,6 +376,27 @@ class WebView extends React.Component {
365376
'always',
366377
'compatibility'
367378
]),
379+
380+
/**
381+
* Override the native component used to render the WebView. Enables a custom native
382+
* WebView which uses the same JavaScript as the original WebView.
383+
*/
384+
nativeConfig: PropTypes.shape({
385+
/*
386+
* The native component used to render the WebView.
387+
*/
388+
component: PropTypes.any,
389+
/*
390+
* Set props directly on the native component WebView. Enables custom props which the
391+
* original WebView doesn't pass through.
392+
*/
393+
props: PropTypes.object,
394+
/*
395+
* Set the ViewManager to use for communcation with the native side.
396+
* @platform ios
397+
*/
398+
viewManager: PropTypes.object,
399+
}),
368400
};
369401

370402
static defaultProps = {
@@ -412,10 +444,14 @@ class WebView extends React.Component {
412444
webViewStyles.push(styles.hidden);
413445
}
414446

447+
const nativeConfig = this.props.nativeConfig || {};
448+
449+
const viewManager = nativeConfig.viewManager || RCTWebViewManager;
450+
415451
var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => {
416452
var shouldStart = this.props.onShouldStartLoadWithRequest &&
417453
this.props.onShouldStartLoadWithRequest(event.nativeEvent);
418-
RCTWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
454+
viewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
419455
});
420456

421457
var decelerationRate = processDecelerationRate(this.props.decelerationRate);
@@ -429,8 +465,10 @@ class WebView extends React.Component {
429465

430466
const messagingEnabled = typeof this.props.onMessage === 'function';
431467

468+
const NativeWebView = nativeConfig.component || RCTWebView;
469+
432470
var webView =
433-
<RCTWebView
471+
<NativeWebView
434472
ref={RCT_WEBVIEW_REF}
435473
key="webViewKey"
436474
style={webViewStyles}
@@ -451,6 +489,7 @@ class WebView extends React.Component {
451489
allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback}
452490
mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
453491
dataDetectorTypes={this.props.dataDetectorTypes}
492+
{...nativeConfig.props}
454493
/>;
455494

456495
return (
@@ -590,15 +629,7 @@ class WebView extends React.Component {
590629
}
591630
}
592631

593-
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
594-
nativeOnly: {
595-
onLoadingStart: true,
596-
onLoadingError: true,
597-
onLoadingFinish: true,
598-
onMessage: true,
599-
messagingEnabled: PropTypes.bool,
600-
},
601-
});
632+
var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig);
602633

603634
var styles = StyleSheet.create({
604635
container: {

0 commit comments

Comments
 (0)