react-native webview loading indicator

Viewed 60320

I am trying to show loading indicator in webweb as follow. Loading indicator is showing but there is white background show after page is loaded. If I change to startInLoadingState to false, web content is showing but loading indicator does not show. It is happing in "react-native": "0.46.3" on ios

renderLoadingView() {
      return (
          <ActivityIndicator
             animating = {this.state.visible}
             color = '#bc2b78'
             size = "large"
             style = {styles.activityIndicator}
             hidesWhenStopped={true} 
          />
      );
}
<WebView
    source={source} 
    renderLoading={this.renderLoadingView} startInLoadingState={true} />
11 Answers

I like this approach which shows the activity indicator overlayed on the loading Webview so you don't have to wait until the entire page is loaded to start seeing content.

constructor(props) {
  super(props);
  this.state = { visible: true };
}

hideSpinner() {
  this.setState({ visible: false });
}

render() {
  return (
    <View style={{ flex: 1 }}>
      <WebView
        onLoad={() => this.hideSpinner()}
        style={{ flex: 1 }}
        source={{ uri: this.props.navigation.state.params.url }}
      />
      {this.state.visible && (
        <ActivityIndicator
          style={{ position: "absolute", top: height / 2, left: width / 2 }}
          size="large"
        />
      )}
    </View>
  );
}

A nice approach is setting the property startInLoadingState to true and set the renderLoading to return the desired View. See the example below.

displaySpinner() {
  return (
    <View>
      {/* Your spinner code goes here. 
        This one commes from react-native-material-kit library */}
      <SingleColorSpinner />
    </View>
  );
}

render() {
  return (
    <WebView
      startInLoadingState={true}
      source={{ uri: this.state.myUri }}
      renderLoading={() => {
        return this.displaySpinner();
      }}
    />
  );
}

Copy & Pasteable: Minimal Webview Component with Loading Indicator

import React, { Component } from "react";
import { ActivityIndicator} from "react-native";
import { WebView } from "react-native-webview";


// Pass a "uri" prop as the webpage to be rendered
class WebViewScreen extends Component {
  constructor(props) {
    super(props);
    this.state = { visible: true };
  }
  hideSpinner() {
    this.setState({ visible: false });
  }
  render() {
    return (
      <React.Fragment>
        <WebView
          onLoadStart={() => this.setState({ visible: true })}
          onLoadEnd={() => this.setState({ visible: false })}

          // Pass uri in while navigating with react-navigation. To reach this screen use:
          // this.props.navigation.navigate("WebViewScreen", {uri: "google.ca"});
          source={{ uri: this.props.navigation.state.params.uri }} 
        />
        {this.state.visible ? (
          <ActivityIndicator
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              right: 0,
              bottom: 0,
              jusityContent: "space-around",
              flexWrap: "wrap",
              alignContent: "center",
            }}
            size="large"
          />
        ) : null}
      </React.Fragment>
    );
  }
}
export default WebViewScreen;

I've used @AdamG's solution but there have a problem with absolute path. The below solution is set ActivityIndicator to the center but with a different way.

<View style={{ flex: 1 }}>
            <WebView
                onLoad={() => this.hideSpinner()}
                style={{ flex: 1 }}
                source={{ uri: 'yourhtml.html' }}
            />
            <View style={{backgroundColor:'white', height:1}}></View>
            {this.state.visible && (
                <View style={{flex:1, alignItems:'center'}}>
                    <ActivityIndicator
                        size="large"
                    />
                </View>
            )}
          </View>

There is 2 another {flex:1} View and ActivityIndicator is in top of the bottom View. I've centered that.

  <View style={{backgroundColor:'white', height:1}}></View>

And this line is set the opacity when you have loading state, there have two different View. In top view there is WebView and there is a black bottom border View belong to the WebView.For closing I've patched it with a white helper view.

Hey bro this is my solution, you have to use the event onLoadEnd instead onLoad, the event onLoad is not working for me.

import React, { Component } from 'react';
import { StyleSheet, ActivityIndicator, View } from 'react-native';
import { WebView } from "react-native-webview";

export default class MainActivity extends Component {
  constructor(props) {
    super(props);
    this.state = { visible: true };
  }

  showSpinner() {
    console.log('Show Spinner');
    this.setState({ visible: true });
  }

  hideSpinner() {
    console.log('Hide Spinner');
    this.setState({ visible: false });
  }

  render() {
    return (
      <View
        style={this.state.visible === true ? styles.stylOld : styles.styleNew}>
        {this.state.visible ? (
          <ActivityIndicator
            color="#009688"
            size="large"
            style={styles.ActivityIndicatorStyle}
          />
        ) : null}

        <WebView
          style={styles.WebViewStyle}
          //Loading URL
          source={{ uri: 'https://aboutreact.com' }}
          //Enable Javascript support
          javaScriptEnabled={true}
          //For the Cache
          domStorageEnabled={true}
          //View to show while loading the webpage
          //Want to show the view or not
          //startInLoadingState={true}
          onLoadStart={() => this.showSpinner()}
          onLoad={() => this.hideSpinner()}
        />
      </View>
    );
  }
}
const styles = StyleSheet.create({
  stylOld: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  styleNew: {
    flex: 1,
  },
  WebViewStyle: {
    justifyContent: 'center',
    alignItems: 'center',
    flex: 1,
    marginTop: 40,
  },
  ActivityIndicatorStyle: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'absolute',
  },
});

react-native webview is now deprecated.
You can import react-native-webview and do the following:

    <WebView
    source={{ uri: 'https://reactnative.dev' }}
    startInLoadingState={true}
    renderLoading={() => <Loading />}
    />

If you want to show a Spinner and then replace that spinner with the WebView already loaded, this is your answer:


    import React from 'react';
    import { StyleSheet, ActivityIndicator, View } from 'react-native';
    import { WebView } from "react-native-webview";
    
    function MyApp() {
    const Spinner = () => (
        <View style={styles.activityContainer}>
          <ActivityIndicator size="large" color={white} />
        </View>
    );
    
    return (
    <WebView
            bounces={false}
            startInLoadingState={true}
            renderLoading={Spinner}
            style={styles.container}
            source={{ uri: yourURL }}
            showsHorizontalScrollIndicator={false}
            scalesPageToFit
          />
    )
    }
    
    
    export default StyleSheet.create({
      container: {
        flex: 1
      },
      activityContainer: {
        alignItems: 'center',
        justifyContent: 'center',
        position: 'absolute',
        top: 0,
        left: 0,
        backgroundColor: black,
        height: '100%',
        width: '100%'
      }
    });

My response will not directly answer your question but it will get you to think about using Skeleton loaders in place of traditional loading indicators (ie. spinner). I've replaced all my project's indicators with Skeletons and have received great user feedback since the change.

Here's a detailed explanation: https://mechanicalrock.github.io/2022/07/11/replace-circular-loaders-with-skeletons-a.html

Hope this plants the seed as a pattern going forward.

<WebView style={{ flex: 1 }} startInLoadingState={true} source={{ uri: "https://google.com" }} renderLoading={() => ( )} />

Related