3

I'm using a webview in react native and I want to display a custom error message when some error occurs (e.g. no internet connection).

My code:

<WebView 
  renderError={() => (
    <MissingConnection />
  )}
  other params....
/>

When there is an error loading the webpage, for a fraction of a second the webview displays the default android error, such as: enter image description here then my MissingConnection component pops up, hiding the webview.

Is there a way to completely remove the default android error screen? It flashes for just a fraction of a second and the result feels really wrong.

gbalduzzi
  • 9,356
  • 28
  • 58

3 Answers3

2

First of all: You are doing it right.

I observed the same problem recently and did some investigation. The problem lies not in your code or React Native or react-native-webview.

This is just the default behavior for Android's WebView. Many Java developers encounter the same problem, examples for related threads on SO:

webview error while loading a page without internet

Prevent WebView from displaying "web page not available"

Android WebView onReceivedError()

The usual proposals to work around are:

  • check the internet connection before trying to load anything (prevent failing)

  • remove the error content quickly and show your own content in the onReceivedError ( which basically maps to your renderError method in react-native-webview). Sometimes with loading a local url like done in Java here.

  • take care of having an overlay which is removed in case there is no error at all. react-native-webview does it the other way round, showing an overlay when there is an error. But the Activity indicator overlay is a good example, it says until loading has finished or encountered an error.

As far as I know there is nothing we can do about except these disappointing ways, as I would prefer not to fight against the system.

Edit: Firefox Focus for Android does the same with quickly replacing content in the error handler.

Thats done in Java in their source here:

https://github.com/mozilla-mobile/focus-android/blob/c789362b9c331b2036755a8398e3770be43b50b8/app/src/main/java/org/mozilla/focus/webview/FocusWebViewClient.java#L330

and

https://github.com/mozilla-mobile/focus-android/blob/63339d2d9a5d132bf4a1fffc4c46c0ce393abe87/app/src/main/java/org/mozilla/focus/webview/ErrorPage.java#L126.

So I assume we are in good company!

Edit 2: I am curious if this is really visible when not in debug mode on a real Android device. My educated guess is that the code executes way faster and it shouldn`t be visible at all. Btw this page is probably only shown for 404 (Not found) errors which are unlikely if you use hardcoded urls and your own servers.

Edit 3: The native error page is visible running on a real device in release mode. The only way to prevent this flickering would be to create an overlay. I opened an issue related to another error which also addresses this one with react-native-webview here https://github.com/react-native-community/react-native-webview/issues/474#issuecomment-487022106.

Frederik Winkelsdorf
  • 4,383
  • 1
  • 34
  • 42
  • 1
    Nice answer. I will do some testings on a release-compiled apk just out for curiosity – gbalduzzi Apr 05 '19 at 13:10
  • @gbalduzzi You're welcome! I observed the very same recently so I am curious, too. The approach with another overlay seems to be too much overhead, at least for my current project. – Frederik Winkelsdorf Apr 05 '19 at 13:59
2

My solutions is Alert function

import React, { Component } from 'react';
import { Alert } from 'react-native';
import { View, Spinner } from 'native-base';
import { WebView } from 'react-native-webview';

export default class ExampleScreen extends Component {

  displaySpinner() {
    return (
      <View style={{ flex: 1 }}>
        <Spinner color="blue" />
      </View>
    );
  }

  displayError() {
    Alert.alert(
      "no_internet",
      "require_internet_connection",
      [
        { text: 'OK', onPress: () => this.props.navigation.goBack() },
      ],
      { cancelable: false });
  }

  render() {
    return (
      <WebView onError={() => this.displayError()}
        startInLoadingState={true}
        renderLoading={() => {
          return this.displaySpinner();
        }}
        source={{ uri: 'https://example.com' }} />
    );
  }
};

enter image description here

Nijat Aliyev
  • 558
  • 6
  • 15
1

Actually, in my case, this solution worked weel

import React, { useEffect, useRef } from 'react';
import { StyleSheet, View, BackHandler } from 'react-native';
import { colors, variables } from 'utils/theme';
import { WebView } from 'react-native-webview';
import { Button, Spinner, Text } from 'components';
import { fa } from 'utils/constants/locales';

const uri = YOUR_WEB_PAGE_URL

const Loading = () => {
  return (
    <View style={styles.loadingWrapper}>
      <Spinner />
      <Text style={styles.loading}>{fa.proEducation.loading}</Text>
    </View>
  );
};
const Error = ({ reload }) => {
  return (
    <View style={styles.loadingWrapper}>
      <Button
        style={styles.retry}
        label={fa.proEducation.retry}
        primary
        onPress={reload}
      />
    </View>
  );
};

const ProEducation = () => {
  const webview = useRef(null);
  const canGoBackRef = useRef(false);
  const onAndroidBackPress = () => {
    if (canGoBackRef.current && webview.current) {
      webview.current.goBack();
      return true;
    }
    return false;
  };
  useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', onAndroidBackPress);
    return () => {
      BackHandler.removeEventListener('hardwareBackPress', onAndroidBackPress);
    };
  }, []);
  const onNavigationStateChange = ({ canGoBack }) => {
    canGoBackRef.current = canGoBack;
  };
  const reload = () => webview.current.reload();

  return (
    <View style={styles.wrapper}>
      <WebView
        ref={webview}
        source={{ uri }}
        style={styles.webView}
        onNavigationStateChange={onNavigationStateChange}
        javaScriptEnabled
        domStorageEnabled
        renderLoading={() => <Loading />}
        renderError={() => <Error reload={reload} />}
        startInLoadingState
      />
    </View>
  );
};

const styles = StyleSheet.create({
  loading: {
    color: colors.lightBlack,
    fontSize: 15,
    marginTop: 8,
    textAlign: 'center',
  },
  loadingWrapper: {
    backgroundColor: colors.white,
    bottom: 0,
    flex: 1,
    justifyContent: 'center',
    left: 0,
    marginBottom: 'auto',
    marginLeft: 'auto',
    marginRight: 'auto',
    marginTop: 'auto',
    position: 'absolute',
    right: 0,
    top: 0,
  },
  retry: {
    alignSelf: 'center',
    paddingHorizontal: variables.gutter,
    paddingVertical: variables.gutter / 2,
  },
  webView: {
    flex: 1,
  },
  wrapper: {
    backgroundColor: colors.bg,
    flex: 1,
  },
});

export default ProEducation;

Dharman
  • 30,962
  • 25
  • 85
  • 135
Ahmad Khani
  • 850
  • 2
  • 10
  • 15