I am able to login a user successfully using Firebase. In my code, after the user has successfully logged in, they are directed to their profile. My problem is that whenever I refresh the browser, the user is logged out for a split second and then directed back to their profile. If I'm on any other page, the user is completely logged out. I'm quite new to custom hooks and am trying to learn on the job. Here's my code
const [currentUser, setCurrentUser] = useState("")
const [currentUserData, setCurrentUserData] = useState("")
const [loading, setLoading] = useState(false)
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
if (user) {
setCurrentUser(user)
db.collection('users')
.doc(user.uid)
.get()
.then(doc => {
setCurrentUserData(doc.data())
// MUST REARANGE vvvv
history.push('/')
})
setLoading(false)
}
else {
setCurrentUser(null)
setCurrentUserData(null)
}
})
return unsubscribe
}, [history])
Related
I am creating a front end for an API in react native. For signed authentication, I use the FireBase service. Everything is good, However, I have a problem. I want after creating the user, my program stays on the login screen and when on the login screen provide the credential, it should move to the Home screen.
In my case, it navigates to the login screen and then use effect () activate and it navigates to the Home. How can I stay in HomeScreen?
// Registrationscreen
const handleSignUp = () => {
createUserWithEmailAndPassword( auth,email, password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// ...
})
.then( navigation.dispatch(
StackActions.replace('Login', {
user: 'jane',
})
))
.catch((error) => alert(error.message));
};
//logIn Screen
const LoginScreen = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const navigation = useNavigation();
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
if (user) {
navigation.replace("Home");
}
});
return unsubscribe;
}, []);
const handleLogin = () => {
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// ...
})
.catch((error) => alert(error.message));
};
I wanted that after registration of the new user, it should navigate to the login page and Stay there and when we put in the credential and press logIn then it should navigate to the Home Screen.
There's unfortunately a lot of places this could be going wrong but it looks like there's a pretty serious error in your LoginScreen component - the useEffect is only running when the component initially renders. This means that by the time the handleLogin function is called the useEffect doesn't know that it needs to run again - which means the redirect to the home screen never runs.
To temporarily fix this: create a user object in state. When the user gets returned from signInWithEmailAndPassword you can store the user into state. Reminder you'd need to update your dependency array in the useEffect to include the user object.
Simplified code that I am using:
import { useState, useEffect, useContext } from 'react'
import { useRouter } from 'next/router'
import { firestore } from './firebase-config'
import { getDoc, doc } from 'firebase/firestore'
export default function HomePage() {
const router = useRouter()
const user = useContext(AuthContext) // contains user object -> user.user
const [loading, setLoading] = useState(true)
useEffect(() => {
const getData = async() => {
setLoading(true)
const uid = user.user.uid // uid of user in firebase auth
const id = router.query.id // id param of url
const docRef = doc(firestore, `...`)
// doc in a collection that references the above uid and id
const docSnap = await getDoc(docRef)
// get the document from firestore
if (docSnap.exists()) {
importData(docSnap.data()) // add data to store to re-render page
setLoading(false)
} else {
router.push('/main')
// if the user isn't logged in to return to '/'
// ^^ not sure how to do these separately
// if the user is logged in but the document does not exist to return to '/main'
}
}
getData()
}, [router.query, user.user])
return (
<>
{/* */}
</>
)
}
I need to load the document associated with the user's uid and the id param of the currently loaded page, i.e. /main/[id].
These retrieve a Firestore document that is then inserted into the store which causes the HomePage function to re-render to show the data.
uid is found in user.user.uid which is set via onAuthStateChanged in app.js
id is found in router.query.id which is set via useRouter() at the top level
The useEffect() above works, but only temporarily, soon after the data is loaded and the component re-renders, I am linked to '/main' as initially uid and id start as undefined meaning that on the first run of the useEffect hook the else condition is run, it then re-runs as the user and router object is retrieved to load the data, but by the time that has occurred the page is transitioned to './main'.
Would greatly appreciate some help to make this function work.
Additionally, the user should go back to './main' if the document doesn't exist but they are logged in, and if they are not logged in to then be returned to the root ('./')
Thanks in advance!
You can add a loading state for the document retrieval in addition to the loading state that you already have to make sure that the document retrieval is completed before navigating away from the page.
import { firestore } from './firebase-config'
import { getDoc, doc } from 'firebase/firestore'
export default function HomePage() {
const router = useRouter()
const user = useContext(AuthContext) // contains user object -> user.user
const [loading, setLoading] = useState(true)
const [docLoading, setDocLoading] = useState(true)
useEffect(() => {
const getData = async() => {
setLoading(true)
const uid = user.user.uid // uid of user in firebase auth
const id = router.query.id // id param of url
if (!uid) {
setLoading(false)
router.push('/')
return
}
if (!id) {
setLoading(false)
router.push('/main')
return
}
const docRef = doc(firestore, `...`)
// doc in a collection that references the above uid and id
setDocLoading(true)
const docSnap = await getDoc(docRef)
// get the document from firestore
setDocLoading(false)
if (docSnap.exists()) {
importData(docSnap.data()) // add data to store to re-render page
setLoading(false)
} else {
router.push('/main')
}
}
getData()
}, [router.query, user.user])
if (loading || docLoading) {
return <div>Loading...</div>
}
return (
<>
{/* render your component here */}
</>
)
}
So I have managed to fix the issue:
To be able to use uid in the useEffect() hook, onAuthStateChanged is called again rather than using the AuthContext that is created at the top level as this will wait until the user exists
To wait for router.query to be updated you can call router.isReady which returns a Boolean value on whether it has been updated.
Using both of these in this way:
useEffect(() => {
onAuthStateChanged(auth, async (user) => {
if (user) {
if (router.isReady) {
// do stuff -> user exists
} else {
// user exists but the document does not
router.push('/main')
}
}
else {
// user is not logged in
router.push('/')
}
})
}, [router.isReady, router.query])
I would like to know how to store the data in the cache when the user come to my website first time and for the subsequent visit I want to fetch the data from cache and only want to fetch new data/updated data from the server.
Right now, it fetches data every time user comes to my website, which causes a lot of reads, so I want to reduce those reads by storing data in the cache.
My code:
import { useEffect, useState } from "react"
// firebase import
import { collection, limit, onSnapshot, orderBy, query, where } from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let ref = collection(db, c)
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
// unsubscribe to the previous listener before running the side effect again
return () => unsubscribe()
}, [openTab])
return { documents, error, isLoading }
}
I am using Firebase Realtime Database for a site I am developing with React. In a useEffect method, I am using Firebase's get method to receive all the data from the database and it works when I switch from the home page back to the page I am displaying the data on but it doesn't work when I refresh my page. I have tried using an async await function, console.logging everything I could think of, and re-writing the entire code.
This is my useEffect method that fetches an input that was previously saved to the database. If I switch from the 'Journal' Router page to Home page and back, it loads correctly but it doesn't load correctly if I refresh the page. When I refresh, it console.logs 'No Data' but I know the data exists because when I switch between router pages it does load.
useEffect(() => {
const dbRef = ref(getDatabase())
//Fetches dreams from firebase's database
get(child(dbRef, `/${user.uid}/dreams`)).then(snapshot => {
if (snapshot.exists()){
const dreams = snapshot.val()
Object.values(dreams).forEach(dream => {
setUserDreams(prev => [...prev, dream])
})
} else {
console.log('No Data')
}
}).catch(err => {
console.error(err);
})
...
}, [])
The JSON structure of the database is basically this
"USER_ID" : {
"dreams" : [{"RANDOM_UUID" : {...}}],
"tags" : [{"RANDOM_UUID" : {...}}]
}
The user ID is the uid that firebase generates in their user authentication feature and it doesn't change and the random uuid is a random string generated from the firebase uuidv4 method.
This is how the user variable is populated:
import {createContext, useContext, useEffect, useState} from 'react'
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
updateProfile,
onAuthStateChanged
} from 'firebase/auth';
import { auth } from '../firebase-config';
const UserContext = createContext();
export const AuthContextProvider = ({children}) => {
const [user, setUser] = useState({})
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
}
const updateUsername = (username) => {
return updateProfile(auth.currentUser, {
displayName: username
})
}
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password);
}
const logout = () => {
return signOut(auth);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
console.log(currentUser)
setUser(currentUser)
})
return () => {
unsubscribe()
}
}, [])
return (
<UserContext.Provider value={{createUser, user, logout, signIn, updateUsername}}>
{children}
</UserContext.Provider>
)
}
export const UserAuth = () => {
return useContext(UserContext)
}
Sorry if this is a bit weird but I figured out the issue. After logging the user variable in my journal file, I learned that it isn't populated until after that useEffect is ran so I just put user as the dependency variable in my useEffect hook so it waits until it is populated to run that hook.
useEffect(() => {
const dbRef = ref(getDatabase())
//Fetches dreams from firebase's database
get(child(dbRef, `/${user.uid}/dreams`)).then(snapshot => {
if (snapshot.exists()){
const dreams = snapshot.val()
Object.values(dreams).forEach(dream => {
setUserDreams(prev => [...prev, dream])
})
} else {
console.log('No Data')
}
}).catch(err => {
console.error(err);
})
...
}, [user])
This is what worked, the only thing changed was the dependency array. Meaning, the user variable was populated after the useEffect hook ran which is what made me have issues. Thanks for the commenter that helped me out!
I have integrated the #react-native-firebase/auth package for user authentication. On the 'onAuthStateChanged' listener the user object is null without calling the signout. Are there any possible fixes for this?
I resolved same issue. But in React application 🙃
It's happened because u need to wait, before firebase-auth get authenticated user. So, you can create useState with default loading in true position. And while it's loading - display spinner or something else. When user will be load - setLoading to false with useEffect.
My app.js:
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
useEffect(() => {
onAuthStateChanged(auth, (authorizedUser) => {
if (authorizedUser) {
console.log('success sign-in')
setUser(authorizedUser);
setLoading(false);
}
});
}, []);