React Native - Displaying a Loading Screen Component in a Javascript Promise

12,066

In your constructor you should set isLoading to false

export default class Login extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoading: true, // change this to false

Whenever you want to show the loading indicator, you should change isLoading to true before the async call, not on the callback.

You should only change the state via setState (see https://facebook.github.io/react/docs/component-api.html)

 _logInUser() {
    let { isLoading, email, password } = this.state;
    this.setState({isLoading:true}); // move state change here
    auth.signInWithEmailAndPassword(email, password)
      .then(() => {        
        Actions.home;
        this.setState({isLoading:false}); // use setState
      })
      .catch((error) => {
        this.setState({isLoading:false}); // use setState
        switch(error.code) {
          case "auth/wrong-password":
            AlertIOS.alert('Uh oh!', 'Invalid password! Please try again.');
          break;

          case "auth/invalid-email":
            AlertIOS.alert('Uh oh!', 'Invalid email! Please try again.'); 
          break;

          case "auth/user-not-found":
            AlertIOS.alert('Uh oh!', 'Please check your credentials and try again');
          break;
        }
      });
  }
Share:
12,066
szier
Author by

szier

I am a Computer Science major and future Software Engineer at Lyft. I also do independent software consulting in the Bay Area to stay busy during school 😃

Updated on June 06, 2022

Comments

  • szier
    szier almost 2 years

    In the code below (in auth_shared.js), I have a Loading component that I wish to display when the promises signInWithEmailAndPassword() or createUserWithEmailAndPassword() are called (whenever the user hits the "Log in" or "Sign up" buttons).

    To do this, I thought it was best to create a state called isLoading and set it to false initially. In render(), I then check the value of isLoading and determine if I should load the Loading component or the Log in or Sign up fields. If signInWithEmailAndPassword() is called I then set isLoading = true in an attempt to display the Loading component while the promise is validating the users email and password. However, this does not seem to work and I am not sure why! Can someone please provide some insight into this? Thank you. Here is my code:

    loading.js

    import React, { Component } from 'react';
    import {
      ActivityIndicator,
      Text,
      View
    } from 'react-native';
    
    import styles from 'TextbookSwap/app_styles';
    
    export default class Loading extends Component {
      render() {
        return (
          <View style={styles.loadingBG}>
            <ActivityIndicator
              animating={true}
              color="white"
              size="large"
              style={{margin: 15}}
            />
    
            <Text style={styles.loadingText}>
              {this.props.loadingText} 
            </Text>
          </View>
        );
      }
    }
    

    login.js

    import React, { Component } from 'react';
    
    // Components
    import AuthShared from '../auth_shared';
    
    export default class Login extends Component {
    
      render() {
        return (
          <AuthShared login={true}/>
        );
      }
    }
    

    signup.js

    import React, { Component } from 'react';
    
    // Components
    import AuthShared from '../auth_shared';
    
    export default class SignUp extends Component {
    
      render() {
        return (
          <AuthShared login={false}/>
        );
      }
    }
    

    auth_shared.js

    import React, { Component } from 'react';
    import {
      AlertIOS,
      Dimensions,
      Image,
      ScrollView,
      StyleSheet,
      Text,
      TextInput,
      TouchableOpacity,
      View
    } from 'react-native';
    
    import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
    import { Actions } from 'react-native-router-flux';
    
    import firebaseApp from 'TextbookSwap/firebase_setup';
    import styles from 'TextbookSwap/app_styles';
    
    // Components
    import HeaderImage from './header_image';
    import Loading from './loading.js';
    
    // For Firebase Auth
    const auth = firebaseApp.auth();
    
    export default class Login extends Component {
      constructor(props) {
        super(props);
    
        this.state = {
          isLoading: true,
          firstName: '',
          lastName: '',
          email: '',
          password: '',
          passwordConfirmation: ''
        }
      }
    
      componentDidMount() {
        let user = auth.currentUser;
        if (user) {
          console.log(msg)
          Actions.home
        } else {
          return;
        }
      }
    
      render() {
        if (this.state.isLoading) {
          return <Loading loadingText="Loading..." />
        } else {
          return (
            <View style={styles.mainContainer}>
              <KeyboardAwareScrollView 
                style={styles.scrollView}
                keyboardShouldPersistTaps={false}
                automaticallyAdjustContentInsets={true}
                alwaysBonceVertical={false}
              >
                <View style={styles.formInputContainer}>
                  <HeaderImage />
                  {this.props.login ? this.renderLogin() : this.renderSignup()}
                </View>
    
                {this.props.login ? this.renderFooter() : null}
    
              </KeyboardAwareScrollView>
            </View>
          );
        }
      }
    
      renderLogin() {
        return (
          <View>
            {this.renderEmailAndPasswordForms()}
            <View style={styles.authButtonContainer}>
              <TouchableOpacity
                style={styles.authButton}
                onPress={this._logInUser.bind(this)}
              >
                <Text style={styles.actionText}>Log me in!</Text>
              </TouchableOpacity>
            </View>
          </View>
        );
      }
    
      renderSignup() {
        return (
          <View>
            <View style={[styles.formInputWrapper, styles.formInputInlineWrapper]}>
              <View style={{borderColor: '#50514F', borderLeftWidth: 0, borderRightWidth: 0.5, borderTopWidth: 0, borderBottomWidth: 0}}>
                <TextInput
                  style={[styles.formInput, styles.formInputInline]}
                  autoFocus={true}
                  autoCapitalize="none"
                  autoCorrect={false}
                  placeholder="First Name"
                  onChangeText={(firstName) => this.setState({firstName})}
                />
              </View>
    
              <TextInput
                style={[styles.formInput, styles.formInputInline]}
                autoFocus={true}
                autoCapitalize="none"
                autoCorrect={false}
                placeholder="Last Name"
                onChangeText={(lastName) => this.setState({lastName})}
              />
            </View>
            {this.renderEmailAndPasswordForms()}
    
            <View style={styles.formInputWrapper}>
              <TextInput
                style={styles.formInput}
                secureTextEntry={true}
                autoCapitalize="none"
                autoCorrect={false}
                placeholder="Password Confirmation"
                onChangeText={(passwordConfirmation) => this.setState({passwordConfirmation})}
              />
            </View>
    
            <View style={styles.authButtonContainer}>
              <TouchableOpacity
                style={styles.authButton}
                onPress={this._signUpUser.bind(this)}
              >
                <Text style={styles.actionText}>Sign me up!</Text>
              </TouchableOpacity>
            </View>
          </View>
        );
      }
    
      renderEmailAndPasswordForms() {
        return (
          <View>
            <View style={styles.formInputWrapper}>
              <TextInput
                style={styles.formInput}
                autoFocus={true}
                autoCapitalize="none"
                autoCorrect={false}
                placeholder="Email"
                onChangeText={(email) => this.setState({email})}
              />
            </View>
    
            <View style={styles.formInputWrapper}>
              <TextInput
                style={styles.formInput}
                secureTextEntry={true}
                autoCapitalize="none"
                autoCorrect={false}
                placeholder="Password"
                onChangeText={(password) => this.setState({password})}
              />
            </View>
    
          </View>
        );
      }
    
      renderFooter() {
        return (
          <View style={styles.footer}>
            <TouchableOpacity
              style={styles.footerButton}
              onPress={Actions.signup}
            >
              <Text style={styles.actionText}>No account? Create one!</Text> 
            </TouchableOpacity>
          </View>
        );
      }
    
      _logInUser() {
        let { isLoading, email, password } = this.state;
    
        auth.signInWithEmailAndPassword(email, password)
          .then(() => {
            isLoading = true;
            Actions.home;
            isLoading = false;
          })
          .catch((error) => {
            isLoading = false;
            switch(error.code) {
              case "auth/wrong-password":
                AlertIOS.alert('Uh oh!', 'Invalid password! Please try again.');
              break;
    
              case "auth/invalid-email":
                AlertIOS.alert('Uh oh!', 'Invalid email! Please try again.'); 
              break;
    
              case "auth/user-not-found":
                AlertIOS.alert('Uh oh!', 'Please check your credentials and try again');
              break;
            }
          });
      }
    
      _signUpUser() {
        let { firstName, lastName, email, password, passwordConfirmation } = this.state;
        // Check that email, password, and passwordConfirmation are present
        if (!firstName || !lastName || !email || !password || !passwordConfirmation) { 
          AlertIOS.alert('Uh oh!', 'Please fill out all fields'); 
    
        } else if (password == passwordConfirmation) {
    
          auth.createUserWithEmailAndPassword(email, password)
            .then((user) => {
              user.updateProfile({
                displayName: `${firstName} ${lastName}`
              })
              .then(Actions.home)
              .catch((error) => {
                AlertIOS.alert(`${error.code}`, `${error.message}`);
              });
            })
            .catch((error) => {
              AlertIOS.alert(`${error.code}`, `${error.message}`);
            });
    
        } else {
          AlertIOS.alert('Uh oh!', 'Passwords do not match');
        }
      }
    }
    
  • szier
    szier over 7 years
    I gave this a try and now when I hit the "Log me in!" button, the Loading component doesn't display. I'm thinking this is due to the fact that even though isLoading = true, render() doesn't get called again and the if check doesn't occur to determine whether or not the Loading component should be displayed. Do you have a solution or advice as to how I can solve this? Thank you for your help btw!
  • FuzzyTree
    FuzzyTree over 7 years
    @szier try the latest update, the problem may be because you're trying to change the state directly instead of using setState
  • szier
    szier over 7 years
    That worked! Just marked this as the answer. Thank you!