The way I understood firebase, the signOut() is pretty much out of the box use. No params, no arguments, nothing. Just a callback. Has that changed? I'm getting a cannot read email of null error which is not breaking my app, but I don't like the error message being there.
Here's the related screens and their relevant code - Sorry if I over-explain this. Trying not to leave any relevant code out
Account Screen - Logout button resides in the Account page with a simple onPress event to a callback that calls firebase functions from a different file called AuthContext
import { AuthContext } from '../context/AuthContext';
import {db, auth} from '../firebase';
import {onAuthStateChanged} from 'firebase/auth';
import { doc, getDoc } from 'firebase/firestore';
const {handleSignOut } = useContext(AuthContext); //👈 this calls the firebase functions.
// 👇 In case it matters I wrote this to show the user's name and email in this screen
const [user, setUser] = useState({
first: "",
last: "",
email: "",
});
const getUser = () => {
onAuthStateChanged(auth, (user) => {
let userEmail = user.email;
user !== null || user !== undefined
const docRef = doc(db, 'useraccounts', userEmail)
getDoc(docRef)
.then((docSnap) => {
if (docSnap.exists()){
setUser(docSnap.data());
} else {
console.log("nothing here");
}
});
});
}
useEffect(() => {
getUser();
},[])
// 👆 End of get User related code ignore if irrelevant
// The logginOut function being that fires the handleSignOut callback
const logginOut = () => {
handleSignOut();
}
<TouchableOpacity onPress={logginOut}>
Text style={styles.btnPrimaryText}> Logout </Text>
</TouchableOpacity>
The AuthContext Screen - I am using AsyncStorage to persist the user's login status so they dont have to login every time the app is closed. I sense this is the culprit, but the Logout is supposed to terminate that.
import React, {createContext, useState, useEffect} from 'react'
import AsyncStorage from '@react-native-async-storage/async-storage';
import {auth, db} from '../firebase';
import { signInWithEmailAndPassword, signOut, deleteUser, createUserWithEmailAndPassword } from "firebase/auth";
import { collection, doc, setDoc } from 'firebase/firestore';
export const AuthContext = createContext()
export const AuthProvider = ({children}) => {
const [userToken, setUserToken] = useState("");
const [userInfo, setUserInfo] = useState("");
const [error, setError] = useState("");
const user = auth.currentUser;
const handleLogin = async (email, password) => {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
await AsyncStorage.setItem('userInfo', JSON.stringify(user));
await setUserInfo(user);
await setUserToken(user.accessToken);
} catch (error) {
console.log(error.message);
setError(error.message);
}
}
if(userToken) {
AsyncStorage.setItem('userToken', userToken);
}
const createAccount = (email, password, first, last) => {
createUserWithEmailAndPassword(auth, email, password, first, last)
.then( async () => {
const newUserRef = collection(db, "useraccounts");
console.log("userid:", auth.currentUser.uid);
await setDoc(doc(newUserRef, auth.currentUser.uid), {
first: first,
last: last,
email: email.toLowerCase(),
user_id: auth.currentUser.uid
});
})
.then(() => {
handleLogin(email, password);
// console.log('User created', user);
})
.catch((error) => {
console.error(error.message);
setError(error.message);
});
}
const handleSignOut = async () => {
try {
await signOut(auth)
await AsyncStorage.removeItem('userToken')
await AsyncStorage.removeItem('userInfo');
setUserToken(null);
} catch (err) {
// console.error(`Error signing out ${err}`);
}
}
const isLoggedIn = async () => {
try {
let userToken = await AsyncStorage.getItem('userToken');
let userInfo = await AsyncStorage.getItem('userInfo');
userInfo = JSON.parse(userInfo);
setUserToken(userToken);
if(userInfo) {
setUserToken(userToken);
setUserInfo(userInfo);
}
} catch (err) {
console.log(`Loggedin Error ${err}` );
}
}
const deleteAccount = async () => {
try {
await deleteUser(user).then(() => {
console.log('User deleted');
signOut(auth);
});
await AsyncStorage.removeItem('userToken');
await AsyncStorage.removeItem('userInfo');
await setUserToken(null);
await setUserInfo(null);
} catch (err) {
console.log(`Error deleting user ${err}`);
}
}
useEffect(() => {
isLoggedIn();
}, [])
return (
<AuthContext.Provider value={{ handleLogin, handleSignOut, deleteAccount, createAccount, userToken, userInfo, error}}>
{children}
</AuthContext.Provider>
);
}
The problem is in your auth state observer onAuthStateChanged:
onAuthStateChanged(auth, (user) => {
let userEmail = user.email;
That second line is causing the error "Cannot read property 'email' of null". The user object is null, and you can't read properties of a null object.
When you call signOut
, the user is immediately signed out, and the observer is invoked with with a null user object to indicate that change. This is documented and expected behavior. Your observer should check if the user object is null before doing anything with it.
See the example in the documentation:
onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/auth.user
const uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
You should also remove the observer when you're done with it, otherwise it will keep triggering even after the React component is unmounted. onAuthStateChanged returns an unsubscribe function that you should call in that case.