React Native登录/登出状态不自动更新,需刷新应用的问题排查
问题:React Native登录/登出后需刷新才能跳转页面
我有一个集成登录、登出功能的React Native应用,功能可正常运行,但完成登录或登出操作后,必须刷新应用才能跳转到对应页面,怀疑是代码导致页面无法自动渲染切换,相关代码如下:
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> ); }
修复方案
1. 异步操作的状态更新时机错误
当前代码中setLoading(false)直接写在异步方法外部,会导致登录/登出操作还未完成就提前更新状态,同时没有保证user状态更新的时机。需要将setLoading(false)移到异步回调的then和catch内部,确保操作完成后再更新状态。
2. 修改后的useAuth.js关键代码
const onLoginPress = (email, password) => { setLoading(true); signInWithEmailAndPassword(auth, email, password) .then((userCredential) => { const user = userCredential.user; console.warn("signed in"); console.warn(user); setLoading(false); // 操作完成后更新状态 }) .catch((error) => { const errorCode = error.code; if (errorCode === "auth/user-not-found") { console.warn("User not found"); } else if (errorCode === "auth/wrong-password") { console.warn("Wrong password"); } else { console.warn(error); } setLoading(false); // 错误时也更新状态 }); }; const onRegisterPress = (email, password, confirmPassword) => { setLoading(true); if (password !== confirmPassword) { alert("Passwords don't match."); setLoading(false); // 提前返回时更新状态 return; } createUserWithEmailAndPassword(auth, email, password) .then((userCredential) => { const user = userCredential.user; alert("Welcome"); setLoading(false); // 操作完成后更新状态 }) .catch((error) => { alert(error); setLoading(false); // 错误时更新状态 }); }; const signOut = () => { setLoading(true); auth.signOut() .then(() => { console.warn("user signed out"); setLoading(false); // 登出完成后更新状态 }) .catch((error) => { console.warn(error); setLoading(false); // 错误时更新状态 }); };
3. 原理说明
auth.onAuthStateChanged监听用户登录状态变化,当登录/登出操作完成后,会自动触发回调更新user状态- StackNavigator组件依赖
user状态进行条件渲染,user状态变化会触发组件重新渲染,自动切换到对应页面 - 修正异步操作的状态更新时机后,确保
user状态的更新能被正确捕获,避免页面无法自动切换的问题
内容的提问来源于stack exchange,提问作者songexile9
相关产品推荐
相关产品推荐

