40

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} />
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Alex Aung
  • 2,637
  • 5
  • 34
  • 63

11 Answers11

64

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>
  );
}
GollyJer
  • 23,857
  • 16
  • 106
  • 174
AdamG
  • 2,570
  • 3
  • 24
  • 35
  • 1
    Awesome solution! To make activity indicator really centered (sorry for being fussy), do `- 18` from both top and left (large activity indicator is fixed at 36x36). – Nikola Mihajlović Dec 18 '18 at 09:14
  • 9
    Best solution so far! To center loading just add `style={{ position: 'absolute', left: 0, right: 0, bottom: 0, top: 0, }}` – codajoao Dec 04 '19 at 04:32
21

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();
      }}
    />
  );
}
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
mjrduran
  • 700
  • 6
  • 8
  • 4
    Unless I'm missing something, this only displays the loading indicator on the initial page load correct? (It won't re-display it if the user clicks a link within the webview.) – Andy Corman Jan 15 '20 at 02:20
8

I have steped on that problem and after some research i found a pretty good solution.

It requires the "react-native-loading-spinner-overlay"

npm install --save react-native-loading-spinner-overlay

index.android.js

import Spinner from 'react-native-loading-spinner-overlay';

const main = 'http://www.myURI.pt';

class MyApp extends Component {
    constructor(props) {
        super(props);
        this.state = { uri: main, visible: true };
    }

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

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

    render() {
        return (
            <View>
                <Spinner
                    visible={this.state.visible}
                    textContent={'Loading...'}
                    textStyle={{ color: '#FFF' }}
                />
                <WebView
                    scalesPageToFit
                    source={{ uri: this.state.uri }}
                    onLoadStart={() => (this.showSpinner())}
                    onLoad={() => (this.hideSpinner())}
                />
            </View>
        );
    }
}

I think i didn't miss any line.

Tiago_nes
  • 843
  • 7
  • 30
  • 1
    This works great but you should link the github repo found at https://github.com/joinspontaneous/react-native-loading-spinner-overlay – Yuri Scarbaci Jun 12 '18 at 15:45
4

Alter your renderLoadingView function to the following, and the loading indicator should work as desired:

renderLoadingView() {
  return (
    <ActivityIndicator
      color='#bc2b78'
      size='large'
      styles={styles.activityIndicator}
    />
  );
}

So essentially, just remove the animating (as it is not required for the given usage) and hidesWhenStopped props from your ActivityIndicator. Hope this helps.

Raunaqss
  • 1,265
  • 12
  • 17
4

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;
codejays
  • 713
  • 6
  • 9
4

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 />}
    />
Mark Reid
  • 2,611
  • 3
  • 23
  • 45
Shawn
  • 351
  • 1
  • 3
  • 9
4

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%'
      }
    });
Matt
  • 195
  • 7
3

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.

eemrah
  • 1,603
  • 3
  • 19
  • 37
3

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',
  },
});
Esteban Contreras
  • 493
  • 1
  • 4
  • 12
0

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.

-1

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

  • Hi and welcome to Stack Overflow! Please take the [tour](https://stackoverflow.com/tour). Thanks for answering but can you also add an explanation on how your code solves the issue? Check the [help center](https://stackoverflow.com/editing-help) for info on how to format code. – Tyler2P Jan 07 '21 at 15:25