How do I overlay ActivityIndicator in react-native?

Here is a complete example using create react native app.

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

export default class Example extends React.Component {

    constructor(props) {
        super(props);

        this.state = {}

    render() {
        return (
            <View
                style={{flex: 1}}
            >
                  //Add other content here
                  {this.state.loading &&
                  <View style={styles.loading}>
                      <ActivityIndicator/>
                  </View>
                  }
                </View>
            );
        }
    }

    showLoading() {
       this.setState({loading: true})
    }

    hideLoading() {
       this.setState({loading: false})
    }

    const styles = StyleSheet.create({
        loading: {
            position: 'absolute',
            left: 0,
            right: 0,
            top: 0,
            bottom: 0,
            opacity: 0.5,
            backgroundColor: 'black',
            justifyContent: 'center',
            alignItems: 'center'
        }
    })

For this to work, you'd need to absolute position it, and render it after the elements that should be underneath the overlay:

  loading: {
    position: 'absolute',
    left: 0,
    right: 0,
    top: 0,
    bottom: 0,
    alignItems: 'center',
    justifyContent: 'center'
  }

Then simply compose it into the render method conditionally, based on a loading state. I am going to assume this.handleLogin sets some sort of loading state already.

Make sure it's rendered last so it takes precedence.

...
{this.state.loading &&
    <View style={styles.loading}>
      <ActivityIndicator size='large' />
    </View>
}

You can use StyleSheet.absoluteFill to shorten code. Add this to your render:

<View style={styles.container}>
  //... other code here
  {this.state.loading && <View
      style={{
        ...StyleSheet.absoluteFill,
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <ActivityIndicator />
  </View>}
</View>

Improvement:

You can also create a Loading component:

Loading.js

import React from 'react';
import {View, ActivityIndicator, StyleSheet} from 'react-native';

export const Loading = ({theme = 'white', size = 'large'}) => {
  const color = theme === 'white' ? '#00bdcd' : '#fff';
  return (
    <View
      style={{
        ...StyleSheet.absoluteFill,
        justifyContent: 'center',
        alignItems: 'center',
      }}>
      <ActivityIndicator size={size} color={color} />
    </View>
  );
};

Then use it anywhere you want

<View style={styles.container}>
   //... other code here
   // remember to import Loading component
   {this.state.loading && <Loading />}
</View>

You can build a nice overlay using the activity indicator component by also leveraging the modal capabilities like Sanaur suggests.

For example you can use the below functional component. You can control it's visibility through the show prop that you can tie to a state in your screen.

An example that you can adapt to your needs.

const ModalActivityIndicator = props => {
  const {
    show = false,
    color = "black",
    backgroundColor = "white",
    dimLights = 0.6,
    loadingMessage = "Doing stuff ..."
  } = props;
  return (
    <Modal transparent={true} animationType="none" visible={show}>
      <View
        style={{
          flex: 1,
          alignItems: "center",
          justifyContent: "center",
          backgroundColor: `rgba(0,0,0,${dimLights})`
        }}
      >
        <View
          style={{
            padding: 13,
            backgroundColor: `${backgroundColor}`,
            borderRadius: 13
          }}
        >
          <ActivityIndicator animating={show} color={color} size="large" />
          <Text style={{ color: `${color}` }}>{loadingMessage}</Text>
        </View>
      </View>
    </Modal>
  );
};

and in your screen, in the render's return, just add it there as a child (please ignore the rest of the code, I put it there for context).

return (
<TouchableWithoutFeedback
  onPress={() => {
    Keyboard.dismiss();
  }}
>
  <View style={{ padding: 13, flex: 1}}>
    <ModalActivityIndicator show={screenIsWaiting} />
    <View
      style={{

where screenIsWaiting is just a state, for example

  const [screenIsWaiting, setScreenIsWaiting] = useState(false);

To test it you can add a button somewhere,

  <Button
    title="TestButton"
    onPress={async () => {
      setScreenIsWaiting(true);
      await sleep(5000);
      setScreenIsWaiting(false);
      ...
    }}
  />

where sleep is a function defined as

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

I found the sleep() idea on stackoverflow on another post.

You can of course also define the

<ModalActivityIndicator show={screenIsWaiting} ... />

only once in your App's root component and trigger it's display and props via a global state container like redux.

Tags:

React Native