React Native - open links in browser

React Native

React Native Problem Overview


Hi i am using react native's webview to display some html, i want that whenever a user clicks a link inside that html, it will open the user's browser with that link.

is that possible?

Edit 1:

I ended up using this package : npmjs.com/package/react-native-communications which opens the browser. I call the browser to open on onNavigationStateChange when the URL changes.

The thing now is that the WebView still continues to process the request although I have moved to the browser, how can i stop the request?

React Native Solutions


Solution 1 - React Native

Here is a complete working solution:

import React, { Component } from 'react';
import { WebView, Linking } from 'react-native';

export default class WebViewThatOpensLinksInNavigator extends Component {
  render() {
    const uri = 'http://stackoverflow.com/questions/35531679/react-native-open-links-in-browser';
    return (
      <WebView
        ref={(ref) => { this.webview = ref; }}
        source={{ uri }}
        onNavigationStateChange={(event) => {
          if (event.url !== uri) {
            this.webview.stopLoading();
            Linking.openURL(event.url);
          }
        }}
      />
    );
  }
}

It uses a simple WebView, intercepts any url change, and if that url differs from the original one, stops the loading, preventing page change, and opens it in the OS Navigator instead.

ios simulator

Solution 2 - React Native

A big issue with using "stopLoading()" is that on Android it disables further taps on any other links from that source page.

The WebView component is being split out of core RN and into the community's hands. If you use that version instead (https://github.com/react-native-community/react-native-webview), you can use "onShouldStartLoadWithRequest" prop on both iOS and Android, which makes this a lot more elegant.

Building off of Damien Varron's really helpful answer, here's an example for how you'd leverage that prop to avoid stopLoading that works cross platform:

onShouldStartLoadWithRequest={event => {
    if (event.url !== uri) {
        Linking.openURL(event.url)
        return false
    }
    return true
}}

And here's how you might do it if your source is HTML as opposed to a URI:

onShouldStartLoadWithRequest={event => {
    if (event.url.slice(0,4) === 'http') {
        Linking.openURL(event.url)
        return false
    }
    return true
}}

As basbase pointed out, you could also do it this way (I've added the about:blank part). I plan to trial and error more to see which holds up best.

onShouldStartLoadWithRequest={event => {
    if (!/^[data:text, about:blank]/.test(event.url)) {
        Linking.openURL(event.url)
        return false
    }
    return true
}}

Solution 3 - React Native

Linking.openURL(url).catch(err => console.error('An error occurred', err));

https://facebook.github.io/react-native/docs/linking.html

Solution 4 - React Native

This is my solution. As it is not generic, you can improve the injected javascript according to your requirements

import {
  View,
  WebView,
  Linking,
} from 'react-native';

const injectScript = `
  (function () {
    window.onclick = function(e) {
      e.preventDefault();
      window.postMessage(e.target.href);
      e.stopPropagation()
    }
  }());
`;

class MyWebView extends React.Component {

  onMessage({ nativeEvent }) {
    const data = nativeEvent.data;

    if (data !== undefined && data !== null) {
      Linking.openURL(data);
    }
  }

  render() {
    return (
      <WebView
        source={{ html: this.props.html }}
        injectedJavaScript={injectScript}
        onMessage={this.onMessage}
      />
    )
  }
}

Solution 5 - React Native

I found a way to do this, it is an iOS-only solution though!

Here is step-by-step instructions:

  1. Link the 'RCTLinkingIOS' library to your project. I used CocoaPods to so.

  2. In your WebView add "onShouldStartLoadWithRequest". For example

    <WebView source={{ html: content }} onShouldStartLoadWithRequest={this.onShouldStartLoadWithRequest} />

  3. Add the this.onShouldStartLoadWithRequest function. Return TRUE or FALSE based on whether or not you want to follow the link in your WebView. This is basically the equivalent of the UIWebViewDelegate implementation you would use when you implement it in native code (Swift or Objective-C).

    onShouldStartLoadWithRequest = (event) => {
         Linking.canOpenURL(event.url).then(supported => {
             if (supported) {
                 Linking.openURL(event.url);
             } else {
                 console.log('Don\'t know how to open URI: ' + event.url);
             }
             return false
         });
    }
    
  4. Make sure to import Linking as well in your javascript source:

    import { AppRegistry, View, WebView, Linking } from 'react-native';

That should do the trick.

For more information see the react-native docs: https://facebook.github.io/react-native/docs/linking.html

Solution 6 - React Native

@Damusnet's only worked sporadically for me. I looked into it and here's my updated code which works for me using react-native-webview 11.4.3.

import React from 'react';
import { WebView, Linking } from 'react-native';

const WebviewScreen = () => {
  const uri = 'http://stackoverflow.com/questions/35531679/react-native-open-links-in-browser';

  return (
    <WebView
    source={{ uri }}
    onShouldStartLoadWithRequest={(request) => {
      if (request.url !== uri) {
        Linking.openURL(request.url);
        return false;
      }

      return true;
    }}
  />    
  )
}

Solution 7 - React Native

<WebView
      source={{uri: this.state.src}}
      ref={(webView) => {
        this.webView.ref = webView;
      }}
      onNavigationStateChange={(navState) => {
        this.webView.canGoBack = navState.canGoBack;
        if (!navState.url.includes('yourdomain.com')) {
          Linking.openURL(navState.url);
          return false;
        }
      }}
      onShouldStartLoadWithRequest={(event) => {
        if (!event.url.includes('yourdomain.com')) {
          Linking.openURL(event.url);
          return false;
        }
        return true;
      }}
      style={{flex: 1}}
      startInLoadingState={true}
      renderLoading={() => {
        return (
          <View style={[styles.container, styles.horizontal]}>
            <ActivityIndicator size="large" color="#0000ff" />
          </View>
        );
      }}
    />

I struggled with this one, my use case was to open external links in a separate browser. This solution worked for me.

Solution 8 - React Native

Solution for those who use expo:

import * as WebBrowser from 'expo-web-browser';
import { WebView } from 'react-native-webview';

...

render() {
    
    return (
        <WebView

            source={{
                uri: uri
            }}

            ref={ (ref) => { this.webview = ref; } }

            onNavigationStateChange={ (event) => {
                if (event.url !== uri) {
                    this.webView.stopLoading();
                    WebBrowser.openBrowserAsync(event.url);
                }
            }}
        />
    );
}

WebBrowser is installed via: expo install expo-web-browser

Solution 9 - React Native

I had many issues with this solution while using local HTML file only at iOS. For instance, when I had Instagram embed in my webview it opened it automatically. In order to solve it I've added (only for iOS):

onShouldStartLoadWithRequest={event => {
    const condition = isIos ? event.navigationType == 'click' : true
    if (event.url.slice(0, 4) === 'http' && condition) {
      Linking.openURL(event.url)
      return false
    }
  return true
}}

Solution 10 - React Native

It's possible to do this in a cross-platform way using the built-in Linking class and react-native-webview-bridge.

const generateLink = (text, url) => {
  if (url) {
    return `<a href='#' onclick='WebViewBridge.send("${url}"); return false;'>${text}</a>`;
  } else {
    return text;
  }
};

const generateHtml = () => `<!DOCTYPE html><html><body>
  ${generateLink('Link text', 'http://example.com')}
</body></html>`;

With a WebViewBridge component rendered like so:

<WebViewBridge
  javaScriptEnabled
  renderLoading={() => <ActivityIndicator animating size="large" />}
  source={{ html: generateHtml() }}
  onBridgeMessage={url => Linking.openURL(url)}
/>

Solution 11 - React Native

In addition to the excellent answer https://stackoverflow.com/a/40382325/10236907: When using source={{html: '...'}}, you can check for an external url change using: if (!/^data:text/.test(event.url)) {

Solution 12 - React Native

  1. If you are attempting to open a redirect url and you get an error from an android intent such as "err_unknown_url_scheme".

  2. Check what it is trying to open because it may have detected android traffic and attempted to open in the corresponding app.

  3. If you wish for it to open in the webview anyway and you get an intent back from a redirect, intercept it in onNavigationStateChange and rewrite it:

onNavigationStateChange={event => {
  if (event.url !== uri) {
      if (event.url.match('intent://')) {
          SetSearchClick(
              event.url.replace('intent://', 'https://'),
          );
         }
   }
}}

Solution 13 - React Native

You'll want to read this: https://facebook.github.io/react-native/docs/linkingios.html

It is possible, just check the link above & you should be all good!

Other links:

https://github.com/ivanph/react-native-webintent

https://www.npmjs.com/package/react-native-browser

Solution 14 - React Native

If stopLoading() in onNavigationStateChange on Android does not work,

this.webview.stopLoading();
setTimeOut( () => {
  Linking.openURL(event.url)
}, 1000);

It worked well.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatan KadoshView Question on Stackoverflow
Solution 1 - React NativedamusnetView Answer on Stackoverflow
Solution 2 - React NativetimraybouldView Answer on Stackoverflow
Solution 3 - React NativeooolalaView Answer on Stackoverflow
Solution 4 - React NativeCésar Parra MorenoView Answer on Stackoverflow
Solution 5 - React NativeBocaxicaView Answer on Stackoverflow
Solution 6 - React NativeflunderView Answer on Stackoverflow
Solution 7 - React NativewtfbrbView Answer on Stackoverflow
Solution 8 - React NativesergeiView Answer on Stackoverflow
Solution 9 - React NativePorniView Answer on Stackoverflow
Solution 10 - React NativeEliotView Answer on Stackoverflow
Solution 11 - React NativebasbaseView Answer on Stackoverflow
Solution 12 - React NativeCparelloView Answer on Stackoverflow
Solution 13 - React NativeJames111View Answer on Stackoverflow
Solution 14 - React NativeredaraView Answer on Stackoverflow