Opening External Links in Browser in React Native WebView

ismail
4 min readMay 20, 2018

--

React Native WebView comes in handy when you’re seeking to embed a webpage in your app or port some HTML file and render it into a scene.

While browsing through the web view, you may find external links that are set to open in new tabs (in a web browser), or had a nofollow attribute, or whatever the cause for which you don’t want your web view to render these links in your main app.

DOM Side

We may call it the front-end, which is the HTML of the web view, where you have no control (yet) over the DOM.

We will be injecting JavaScript into our web view to access the DOM and override the default click handles for the targeted links. First off, make sure your web view enables JavaScript:

<WebView
ref={WEBVIEW_REF}
source={{uri: 'https://www.google.com'}}
onError={console.error.bind(console, 'error')}
javaScriptEnabled={true}
...
/>

Now that we’ve enabled JavaScript execution on the web view, we can make use of the `injectedJavaScript` property of the WebView to inject custom JavaScript.

(function(){
var attachEvent = function(elem, event, callback)
{
event = event.replace(/^on/g, '');

if ( 'addEventListener' in window ) {
elem.addEventListener(event, callback, false);
} else if ( 'attachEvent' in window ) {
elem.attachEvent('on'+event, callback);
} else {
var registered = elem['on' + event];
elem['on' + event] = registered ? function(e) {
registered(e);
callback(e);
} : callback;
}

return elem;
}
var all_links = document.querySelectorAll('a[href]'); if ( all_links ) {
for ( var i in all_links ) {
if ( all_links.hasOwnProperty(i) ) {
attachEvent(all_links[i], 'onclick', function(e){
if ( ! new RegExp( '^https?:\/\/' + location.host, 'gi' ).test( this.href ) ) {
// handle external URL
e.preventDefault();
console.log(this.href)
}
});
}
}
}
})();

That’s a little too much of code to get started with, isn’t it? Not really. The first part adds a method to register custom events correctly with `attachEvent` helper function, if your embedded document has jQuery in then you can make use of jQuery API instead of native JavaScript. The second part queries all links in the document, and binds a click event to these links in order to check, once a link is clicked, if considered an external or not.

When a URL is considered external, we can then prevent the default event and tell our app to open it in the browser. But how do you determine a link is external?

It depends, for the sake of simplicity here, I am only checking if a link’s path name is not the same as the current site’s. But you can totally change and improve that.

Contacting the App

To instruct the app to open an external link in the browser, we need to communicate with React Native from the DOM. There are many ways of doing this:

We’ll keep it simple and use the first one.

// handle external URL
e.preventDefault();
window.postMessage(JSON.stringify({
external_url_open: this.href
}));

Now the full JavaScript code to be injected becomes:

(function(){
var attachEvent = function(elem, event, callback)
{
event = event.replace(/^on/g, '');
if ( 'addEventListener' in window ) {
elem.addEventListener(event, callback, false);
} else if ( 'attachEvent' in window ) {
elem.attachEvent('on'+event, callback);
} else {
var registered = elem['on' + event];
elem['on' + event] = registered ? function(e) {
registered(e);
callback(e);
} : callback;
}

return elem;
}
var all_links = document.querySelectorAll('a[href]'); if ( all_links ) {
for ( var i in all_links ) {
if ( all_links.hasOwnProperty(i) ) {
attachEvent(all_links[i], 'onclick', function(e){
if ( ! new RegExp( '^https?:\/\/' + location.host, 'gi' ).test( this.href ) ) {
// handle external URL
e.preventDefault();
window.postMessage(JSON.stringify({
external_url_open: this.href
}));
}
});
}
}
}
})();

We will compress that into 1 line prior injecting it.

React Native side

Let’s inject the JavaScript and also bind a callback to the onMessage property:

render() {
let jsCode = `!function(){var e=function(e,n,t){if(n=n.replace(/^on/g,""),"addEventListener"in window)e.addEventListener(n,t,!1);else if("attachEvent"in window)e.attachEvent("on"+n,t);else{var o=e["on"+n];e["on"+n]=o?function(e){o(e),t(e)}:t}return e},n=document.querySelectorAll("a[href]");if(n)for(var t in n)n.hasOwnProperty(t)&&e(n[t],"onclick",function(e){new RegExp("^https?://"+location.host,"gi").test(this.href)||(e.preventDefault(),window.postMessage(JSON.stringify({external_url_open:this.href})))})}();`
return (
<View style={{ flex: 1 }}>
<WebView
ref={WEBVIEW_REF}
source={{uri: 'https://www.google.com'}}
onError={console.error.bind(console, 'error')}
javaScriptEnabled={true}
onMessage={this.onMessage.bind(this)}
injectedJavaScript={jsCode}
style={{ flex: 1 }}
/>
</View>
);
}
onMessage(e)
{
// retrieve event data
var data = e.nativeEvent.data;
// maybe parse stringified JSON
try {
data = JSON.parse(data)
} catch ( e ) { }

// check if this message concerns us
if ( 'object' == typeof data && data.external_url_open ) {
// proceed with URL open request
}
}

To open a URL in the user’s default browser from React Native, you can make use of Linking component:

// import
import { Linking } from 'react-native';
// call
Linking.openURL( some_url );

We also need the user’s consent on this action so we will ask them if they want to open the external URL in their browser, using an alert box, similar to confirm in JavaScript:

// import the Alert component
import { Alert } from 'react-native';
// call
Alert.alert(
'External URL',
'Do you want to open this URL in your browser?',
[
{text: 'Cancel', style: 'cancel'},
{text: 'OK', onPress: () => Linking.openURL( some_url )},
],
{ cancelable: false }
);

Excellent. Now we can stack it all up into our onMessage method:

onMessage(e)
{
// retrieve event data
var data = e.nativeEvent.data;
// maybe parse stringified JSON
try {
data = JSON.parse(data)
} catch ( e ) { }
// check if this message concerns us
if ( 'object' == typeof data && data.external_url_open ) {
// proceed with URL open request
return Alert.alert(
'External URL',
'Do you want to open this URL in your browser?',
[
{text: 'Cancel', style: 'cancel'},
{text: 'OK', onPress: () => Linking.openURL( data.external_url_open )},
],
{ cancelable: false }
);
}
}

Don’t forget to import Linking and Alert from React Native components. That should be it.

--

--