useEffect is re-rendering async promise again and again - reactjs

I am trying to log currentUser details to console to see if user is logged in or not. But it is logging again n again and make a loop of currentUser in console.
Here is the code
const [currentUser, setCurrentUser] = useState(null);
const unSubscribeFromAuth = useRef(null);
useEffect(() => {
unSubscribeFromAuth.current = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
})
console.log(currentUser);
})
}
else {
setCurrentUser(userAuth);
}
})
return () => { unSubscribeFromAuth.current() };
}, [currentUser]);
and here is the function i am importing
export const createUserProfileDocument = async (userAuth, additionalData) => {
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`);
const snapShot = userRef.get();
console.log(snapShot);
if (!snapShot.exists) {
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await userRef.set({
displayName,
email,
createdAt,
...additionalData
})
}
catch (error) {
console.log('error creating user', error.message);
}
}
return userRef;
}
I tried to remove dependency but it says
React Hook useEffect has a missing dependency: 'currentUser'. Either include it or remove the dependency array

Since you are updating currentUser in the useEffect and the useEffect is triggered every time currentUser changes, you are seeing the infinite loop. You need to remove currentUser from the dependency array.
useEffect has a missing dependency is coming from console.log. If you remove it, it will go away.

Related

useState does not update the state within .catch after dispatching of asynk thunk

In the following code setError does not update error in the catch after async thunk from redux-toolkit.
const [error, setError] = useState("");
const handleLogin = () => {
dispatch(userLogin({ email, password }))
.unwrap()
.then(() => {
if (userInfo) {
navigate("/profile");
}
})
.catch((err) => {
setError(err) // does not work
});
};
<button onClick={handleLogin}>Login</button>
export const userLogin = createAsyncThunk(
"auth/login",
async ({ email, password }, thunkAPI) => {
try {
const { data } = await userLoginRequest(email, password);
return data;
} catch (error) {
return thunkAPI.rejectWithValue(ERR_USER_LOGIN);
// ERR_USER_LOGIN is just a constant string from another file
}
}
);
I know that useState does not apply changed immediatly but in my case it ignores changes at all. I suppose that the problem can be related to the scope or something like this. So I've tried to use additional callback which I sent as a parameter and change the state through it but it also does not work.
Your userLogin function actually has caught the error, making handleLogin catch not catch anything.
You can throw the error within userLogin, so handleLogin can catch the error by itself.
export const userLogin = createAsyncThunk(
"auth/login",
async ({ email, password }, thunkAPI) => {
try {
const { data } = await userLoginRequest(email, password);
return data;
} catch (error) {
thunkAPI.rejectWithValue(ERR_USER_LOGIN);
// Add this
throw error;
}
}
);

How to use useNavigate outside react hook?

Gets list of emails from firestore and checks if current user is registered and then redirects them to sign up if they are new user.
The code is functional(it redirects succesfully) but get the following error:
arning: Cannot update a component (BrowserRouter) while rendering a different component You should call navigate() in a React.useEffect(), not when your component is first rendered.
const navigate = useNavigate();
let hasEmail = false;
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
const emailCheck = (emails) => { //checks if email exists
hasEmail = emails.some((e) => e.email === auth.currentUser.email);
};
const direct = () => { // redirects to required page
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
emailCheck(emailList);
direct();
Move the email checking logic into a useEffect hook with a dependency on the emailList state.
const navigate = useNavigate();
const [emailList, setEmailList] = useState([]);
const emailRef = collection(db, "emails");
useEffect(() => {
const getEmails = async () => {
const data = await getDocs(emailRef);
setEmailList(
data.docs.map((doc) => ({
...doc.data(),
}))
);
};
getEmails();
}, []);
useEffect(() => {
if (emailList.length) {
const hasEmail = emailList.some((e) => e.email === auth.currentUser.email);
navigate(hasEmail ? "/index" : "/enterdetails");
}
}, [auth, emailList, navigate]);
This might not run without the proper firebase config but check it out
https://codesandbox.io/s/elated-bell-kopbmp?file=/src/App.js
Things to note:
Use useMemo for hasEmail instead of emailCheck. This will re-run only when emailList changes
const hasEmail = useMemo(() => {
//checks if email exists
return emailList.some((e) => e.email === auth.currentUser.email);
}, [emailList]);
There isn't really a point in having this in a react component if you are just redirecting away. Consider having the content of 'index' at the return (</>) part of this component. Only redirect if they aren't authorized
useEffect(() => {
if (!hasEmail) {
navigate("/enterdetails");
}
//else {
// navigate("/index");
//}
}, [hasEmail, navigate]);

Hi, i'm retrieving data from firestore, and checking whether to direct the user to index page or to enter details for a new user But not able to do so

React code
import React, { useEffect, useState } from "react";
import { getDocs, collection } from "firebase/firestore";
import { auth, db } from "../firebase-config";
import { useNavigate } from "react-router-dom";
function Load() {
const navigate = useNavigate();
const [accountList, setAccountList] = useState([]);
const [hasEmail, setHasEmail] = useState(false);
const accountRef = collection(db, "accounts");
Am i using useEffect correctly?
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
checking whether email exists
const emailCheck = () => {
if (accountList.filter((e) => e.email === auth.currentUser.email)) {
setHasEmail(true);
} else {
setHasEmail(false);
}
};
Redirecting based on current user
const direct = () => {
if (hasEmail) {
navigate("/index");
} else {
navigate("/enterdetails");
}
};
return <div></div>;
}
The code compiles but doesn't redirect properly to any of the pages.
What changes should I make?
First question posted excuse me if format is wrong.
There are two problems here:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
emailCheck();
direct();
}, []);
In order:
Since getAccounts is asynchronous, you need to use await when calling it.
But even then, setting state is an asynchronous operation too, so the account list won't be updated immediately after getAccounts completes - even when you use await when calling it.
If you don't use the accountList for rendering UI, you should probably get rid of it as a useState hook altogether, and just use regular JavaScript variables to pass the value around.
But even if you use it in the UI, you'll need to use different logic to check its results. For example, you could run the extra checks inside the getAccounts function and have them use the same results as a regular variable:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
const result = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setAccountList(result);
emailCheck(result);
direct();
};
getAccounts();
}, []);
const emailCheck = (accounts) => {
setHasEmail(accounts.some((e) => e.email === auth.currentUser.email));
};
Alternatively, you can use a second effect that depends on the accountList state variable to perform the check and redirect:
useEffect(() => {
const getAccounts = async () => {
const data = await getDocs(accountRef);
setAccountList(
data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}))
);
};
getAccounts();
});
useEffect(() => {
emailCheck();
direct();
}, [accountList]);
Now the second effect will be triggered each time the accountList is updated in the state.

How do we check the boolean value in useEffect in react hooks?

I have received the boolean value and set to setNomStatus, but how can I check if that is true to show setShowCalender(true) ?
const [nomStatus, setNomStatus] = useState(false);
useEffect(() => {
const fetchData = async () => {
const email = localStorage.getItem("loginEmail");
try {
const res = await Axios.get(
"http://localhost:8000/service/activeStatus", {email}
);
setNomStatus(res.data[0].status);
console.log("Get status data :" + res.data[0].status);
if(nomStatus == true){
setShowCalender(true);
}
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
You can add another useEffect which watches this change, useEffect takes a second argument which is dependency array and the effect gets called if any of the dependency array value changes .
In this case since you need to make a decision based on the nomStatus, you can add it as a dependency to your useEffect
useEffect(() => {
if (nomStatus) {
setShowCalender(true);
}
}, [nomStatus]);
You can't since React state updates are asynchronously processed, the nomStatus state update won't be available until the next render cycle. Use the res.data[0].status value to set the showCalendar state.
const [nomStatus, setNomStatus] = useState(false);
useEffect(() => {
const fetchData = async () => {
const email = localStorage.getItem("loginEmail");
try {
const res = await Axios.get(
"http://localhost:8000/service/activeStatus",
{email}
);
setNomStatus(res.data[0].status);
console.log("Get status data :" + res.data[0].status);
if (res.data[0].status){
setShowCalender(true);
}
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
Or you can use a second useEffect hook with a dependency on nomStatus state update to set the showCalendar state.
useEffect(() => {
const fetchData = async () => {
const email = localStorage.getItem("loginEmail");
try {
const res = await Axios.get(
"http://localhost:8000/service/activeStatus",
{email}
);
setNomStatus(res.data[0].status);
console.log("Get status data :" + res.data[0].status);
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
useEffect(() => {
if (nomStatus){
setShowCalender(true);
}
}, [nomStatus]);

Prevent `useEffect` from looping

I have a simple useEffect that I'm not sure how to stop from invoking endlessly. It keeps firing the first if conditional endlessly. I've been reading a lot about hooks and I assume (maybe erroneously) that each render of the component results in a new invocation of my useAuth() and useUser() hooks. Since they have new references in memory it's triggering the useEffect's deps since technically it's a new function that exists in the scope of this new component render?
Thats my thought at least, no clue how to fix that if that's indeed that case.
const RootPage = ({ Component, pageProps }): JSX.Element => {
const { logoutUser } = useAuth(); // imported
const { fetchUser } = useUser(); // imported
const router = useRouter();
useEffect(() => {
// authStatus();
const unsubscribe = firebaseAuth.onAuthStateChanged((user) => {
if (user) {
console.log(1);
return fetchUser(user.uid); // async function that fetches from db and updates redux
}
console.log(2);
return logoutUser(); // clears userData in redux
});
return () => unsubscribe();
}, [fetchUser, logoutUser]);
...
}
fetchUser
const fetchUser = async (uid) => {
try {
// find user doc with matching id
const response = await firebaseFirestore
.collection('users')
.doc(uid)
.get();
const user = response.data();
// update redux with user
if (response) {
return dispatch({
type: FETCH_USER,
payload: user,
});
}
console.log('no user found');
} catch (error) {
console.error(error);
}
};
logoutUser
const logoutUser = async () => {
try {
// logout from firebase
await firebaseAuth.signOut();
// reset user state in redux
resetUser();
return;
} catch (error) {
console.error(error);
}
};
when I refresh the page with this useEffect on this is output to the console:
useEffect(() => {
function onAuthStateChange() {
return firebaseAuth.onAuthStateChanged((user) => {
if (user) {
fetchUser(user.uid);
} else {
resetUser();
}
});
}
const unsubscribe = onAuthStateChange();
return () => {
unsubscribe();
};
}, [fetchUser, resetUser]);
Keeping everything the same && wrapping fetchUser and resetUser with a useCallback, this solution seems to be working correctly. I'm not entirely sure why at the moment.

Resources