react-native-webview: Android: Vanilla android error page shown, despite setting renderError()

Bug description:

When using WebView with Android, there is a basic Android error page shown, alongside the content returned by renderError.

class WebView extends React.Component<WebViewProps> {
  webViewRef = null;
  reload = () => {
    if (this.webViewRef) {
      this.webViewRef.reload();
    }
  };
  render() {
    const { source } = this.props;
    return (
      <RNWebView
        ref={webView => (this.webViewRef = webView)}
        source={{ uri: 'blablabla_not_a_real_domain' }}
        dataDetectorTypes="none"
        useWebKit
        renderLoading={() => (
          <CenteredBlock>
            <ActivityIndicator />
          </CenteredBlock>
        )}
        renderError={(error, code, description) => {
          let errorMessage;
          if (Platform.OS === 'ios') {
            errorMessage = description;
          } else {
            errorMessage = 'Something went wrong whilst loading this content.';
          }
          return (
            <CenteredBlock>
              <CenteredRow>
                <WarningIcon width={32} height={32} />
              </CenteredRow>
              <Body centered>
                {errorMessage}
              </Body>
              <CenteredRow>
                <TextLink title="Retry" onPress={this.reload} />
              </CenteredRow>
            </CenteredBlock>
          );
        }}
        startInLoadingState
      />
    );
  }
}

To Reproduce:

Expected behavior:

Render only the content returned by the renderError prop.

Screenshots/Videos:

image

Environment:

  • OS: MacOS / Android
  • OS version: Mojave / Nexus_5X_API_28_x86
  • react-native version: 0.58.6
  • react-native-webview version: 5.8.1

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 19

Most upvoted comments

@ec-harry you can custom your render message

renderError={() => { return ( <View style={{ width: '100%', height: '100%', backgroundColor: 'white', justifyContent: 'center', alignItems: 'center', }} > <Text style={{ color: 'black' }}>{'This is an error message'} </Text> </View> ); }}

Do you think it should return errorMessage without the vanilla android error page??

I don’t want this to be in the UI:

image

I would have expected that supplying a renderError prop would be a way to totally override any UI shown when there is an error.

To solve this issue make your LoadingView and ErrorView have StyleSheet.absoluteFillObject containers.

This is what the code looks like that renders the WebView on both iOS and Android:

<View style={styles.container}>
  {webView}
  {otherView}
</View>

The reason they do this is because webView handles the loading and error callbacks and then the otherView is generated based on the results of those callbacks. In other words you can’t just render the otherView because then the webView could never stop loading via the onLoadingFinish callback if you had an LoadingView rendered.

Another advantage of this approach is that it maintains your web navigation state between errors.

@arcesoj even if you add this, you still get a flash of what @cjpete is showing in their screenshot

… is this forgotten?

I managed to work around something. Basically I introduced a white View with delayed hide so I can bypass that ugly native screen showing between transitions.

Maybe someone can optimise this and translate it into a PR.

WebView.android.js

      ....
      _this.state = {
            viewState: _this.props.startInLoadingState ? 'LOADING' : 'IDLE',
            lastErrorEvent: null,
            intermediateView: true
        };
       ....
        _this.reload = function () {
            _this.setState({
                viewState: 'LOADING',
                intermediateView: true
            });
            UIManager.dispatchViewManagerCommand(_this.getWebViewHandle(), _this.getCommands().reload, undefined);
        };
       ....
        _this.onLoadingStart = function (event) {
            var onLoadStart = _this.props.onLoadStart;
            var url = event.nativeEvent.url;
            _this.startUrl = url;
            if (onLoadStart) {
                onLoadStart(event);
            }
            _this.setState({
                intermediateView: true
            })
            _this.updateNavigationState(event);
        };
        _this.onLoadingError = function (event) {
            event.persist(); // persist this event because we need to store it
            var _a = _this.props, onError = _a.onError, onLoadEnd = _a.onLoadEnd;
            if (onError) {
                onError(event);
            }
            if (onLoadEnd) {
                onLoadEnd(event);
            }
            console.warn('Encountered an error loading page', event.nativeEvent);
            _this.setState({
                lastErrorEvent: event.nativeEvent,
                viewState: 'ERROR',
                intermediateView: true
            });
        };
       ....
        _this.onLoadingFinish = function (event) {
            var _a = _this.props, onLoad = _a.onLoad, onLoadEnd = _a.onLoadEnd;
            var url = event.nativeEvent.url;
            if (onLoad) {
                onLoad(event);
            }
            if (onLoadEnd) {
                onLoadEnd(event);
            }
            if (url === _this.startUrl) {
                _this.setState({
                    viewState: 'IDLE',
                });
            }
            var intermediateViewTimeout = setTimeout(() => {
                _this.setState({
                    intermediateView: false
                }, clearTimeout(intermediateViewTimeout))
            }, 250);
            _this.updateNavigationState(event);
        };

WebView.prototype.render = function () {
        var _a = this.props, onMessage = _a.onMessage, onShouldStartLoadWithRequestProp = _a.onShouldStartLoadWithRequest, originWhitelist = _a.originWhitelist, renderError = _a.renderError, renderLoading = _a.renderLoading, source = _a.source, style = _a.style, containerStyle = _a.containerStyle, _b = _a.nativeConfig, nativeConfig = _b === void 0 ? {} : _b, otherProps = __rest(_a, ["onMessage", "onShouldStartLoadWithRequest", "originWhitelist", "renderError", "renderLoading", "source", "style", "containerStyle", "nativeConfig"]);
        var otherView = null;
        var intermediateView = (
            <View style={
                {
                    position: 'absolute',
                    flex: 1,
                    justifyContent: 'center',
                    alignItems: 'center',
                    height: '100%',
                    width: '100%',
                    backgroundColor: 'white'
                }
            }></View>
        );
        if (this.state.viewState === 'LOADING') {
            otherView = (renderLoading || defaultRenderLoading)();
        }
        else if (this.state.viewState === 'ERROR') {
            var errorEvent = this.state.lastErrorEvent;
            invariant(errorEvent != null, 'lastErrorEvent expected to be non-null');
            otherView = (renderError || defaultRenderError)(errorEvent.domain, errorEvent.code, errorEvent.description);
        }
        else if (this.state.viewState !== 'IDLE') {
            console.error("RNCWebView invalid state encountered: " + this.state.viewState);
        }
        var webViewStyles = [styles.container, styles.webView, style];
        var webViewContainerStyle = [styles.container, containerStyle];
        if (typeof source !== "number" && source && 'method' in source) {
            if (source.method === 'POST' && source.headers) {
                console.warn('WebView: `source.headers` is not supported when using POST.');
            }
            else if (source.method === 'GET' && source.body) {
                console.warn('WebView: `source.body` is not supported when using GET.');
            }
        }
        var NativeWebView = nativeConfig.component || RNCWebView;
        var onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(this.onShouldStartLoadWithRequestCallback, 
        // casting cause it's in the default props
        originWhitelist, onShouldStartLoadWithRequestProp);
        var webView = (<NativeWebView key="webViewKey" {...otherProps} messagingEnabled={typeof onMessage === 'function'} onLoadingError={this.onLoadingError} onLoadingFinish={this.onLoadingFinish} onLoadingProgress={this.onLoadingProgress} onLoadingStart={this.onLoadingStart} onHttpError={this.onHttpError} onMessage={this.onMessage} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} ref={this.webViewRef} 
        // TODO: find a better way to type this.
        source={resolveAssetSource(source)} style={webViewStyles} {...nativeConfig.props}/>);
        return (<View style={webViewContainerStyle}>
        {webView}
        {this.state.intermediateView ? intermediateView : null}
        {otherView}
      </View>);
    };