szier szier - 4 months ago 81
Javascript Question

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

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');
}
}
}

Answer

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;
        }
      });
  }