Skip to content
Advertisement

Why doesn’t the state gets updated in my sign in in and sign out functions?

Currently I have a simple React Native app which works with signing in and signing out. The only problem is that in order for the sign in to complete or for the signing out to complete (ie, take me to the other screen) I must refresh the application.

I was wondering if there is something wrong in my code which stops user rendering which screen to display.

StackNavigator.js:

import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
import { HomeScreen, LoginScreen, RegistrationScreen } from "../screens";
import useAuth from "../hooks/useAuth";

const Stack = createStackNavigator();

const StackNavigator = () => {
  const { user } = useAuth();
  return (
    <Stack.Navigator>
      {user ? ( // if user is logged in we show the home screen
        <Stack.Screen name="Home" component={HomeScreen} />
      ) : (
        <>
          <Stack.Screen name="Login" component={LoginScreen} />
          <Stack.Screen name="Registration" component={RegistrationScreen} />
        </>
      )}
    </Stack.Navigator>
  );
};

export default StackNavigator;

useAuth.js:

import React, { createContext, useContext, useEffect, useState } from "react";
import { auth } from "../model/config";
import {
  signInWithEmailAndPassword,
  createUserWithEmailAndPassword,
} from "firebase/auth";

const AuthContext = createContext({});

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null); //user is initally null
  const [loadingInital, setLoadingInital] = useState(true); //loadingInital is initally true
  const [loading, setLoading] = useState(false); //loading is initally false

  useEffect(() => {
    //This hook allows us to remember the user's login status
    const unsub = auth.onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      } else {
        setUser(null);
      }
      setLoadingInital(false); //after user is fetched, loadingInital is set to false
    });
    return unsub(); //unsubscribe from the auth listener
  }, []);

  const onLoginPress = (email, password) => {
    setLoading(true); //loading is set to true
    //Takes in two arguments, email and password used in LoginScreen class
    signInWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        // Signed in

        const user = userCredential.user;
        console.warn("signed in");
        console.warn(user);
        //Navigate after sign in
        // ...
      })
      .catch((error) => {
        //If any error we will catch
        const errorCode = error.code;

        if (errorCode === "auth/user-not-found") {
          console.warn("User not found");
        }

        if (errorCode === "auth/wrong-password") {
          console.warn("Wrong password");
        } else {
          console.warn(error);
        }
      });
    setLoading(false); //loading is set to false
  };
  const onRegisterPress = (email, password, confirmPassword) => {
    setLoading(true); //loading is set to true
    if (password !== confirmPassword) {
      alert("Passwords don't match.");
      return;
    }
    createUserWithEmailAndPassword(auth, email, password)
      .then((userCredential) => {
        const user = userCredential.user;

        alert("Welcome");
      })
      .catch((error) => {
        alert(error);
      });
    setLoading(false); //loading is set to false
  };

  const signOut = () => {
    setLoading(true); //loading is set to true
    auth.signOut();
    console.warn(user);
    setLoading(false); //loading is set to false
  };

  return (
    <AuthContext.Provider
      value={{ user, onLoginPress, onRegisterPress, signOut, loading }}
    >
      {!loadingInital && children}
    </AuthContext.Provider>
  );
};
//if loadingInital is true, we will render nothing ^

export default function useAuth() {
  return useContext(AuthContext);
}

LoginScreen:

import React, { useState } from "react";
import { Image, Text, TextInput, TouchableOpacity, View } from "react-native";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import styles from "./styles"; //styles
import FINAL_STYLES from ".././../FINAL_STYLES"; //styles main
import { auth } from "../../model/config";
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
import useAuth from "../../hooks/useAuth";

export default function LoginScreen({ navigation }) {
  const { onLoginPress } = useAuth();
  const [email, setEmail] = useState(""); //setting as temp state
  const [password, setPassword] = useState("");

  const onFooterLinkPress = () => {
    navigation.navigate("Registration");
  };

  //Below we return JSX
  return (
    <View style={styles.container}>
      <KeyboardAwareScrollView
        style={{ flex: 1, width: "100%" }}
        keyboardShouldPersistTaps="always"
      >
        <Image
          style={styles.logo}
          source={require("../../../assets/icon.png")}
        />
        <TextInput
          style={styles.input}
          placeholder="E-mail"
          placeholderTextColor="#aaaaaa"
          onChangeText={(text) => setEmail(text)}
          value={email}
          underlineColorAndroid="transparent"
          autoCapitalize="none"
        />
        <TextInput
          style={styles.input}
          placeholderTextColor="#aaaaaa"
          secureTextEntry
          placeholder="Password"
          onChangeText={(text) => setPassword(text)}
          value={password}
          underlineColorAndroid="transparent"
          autoCapitalize="none"
        />
        <TouchableOpacity
          style={[styles.button, { backgroundColor: "#9D3BEA" }]} //TODO: change to global styles
          onPress={() => onLoginPress(email, password)}
        >
          <Text style={styles.buttonTitle}>Log in</Text>
        </TouchableOpacity>
        <View style={styles.footerView}>
          <Text style={styles.footerText}>
            Don't have an account?{" "}
            <Text onPress={onFooterLinkPress} style={styles.footerLink}>
              Sign up
            </Text>
          </Text>
        </View>
      </KeyboardAwareScrollView>
    </View>
  );
}

What would be the best way to get it to update immediately after I sign in? Thank you 🙂

Advertisement

Answer

You have to reload the application to see changes because in onLoginPress you are mutating user state without calling setUser(), while in that useEffect that runs on load you did the correct thing. Change it as below. Notice that setUser(userCredential.user) I added:

const onLoginPress = (email, password) => {
    setLoading(true);
    signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
            setUser(userCredential.user);
        })
        .catch((error) => {
            const errorCode = error.code;
            if (errorCode === "auth/user-not-found") {
                console.warn("User not found");
            }
            if (errorCode === "auth/wrong-password") {
                console.warn("Wrong password");
            } else {
                console.warn(error);
            }
        })
        .finally(() => { // to make sure it runs after the promise has resolved
            setLoading(false);
        });
};

You made the same mistake in onRegisterPress as well, don’t forgot to fix it. Lastly in signOut, call setUser(null) after auth.signOut() to make sure you reset user.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement