Getting user data from Firestore and saving it to state - React - reactjs

I would like to save user's data into state but for some reason is not working
I created this function to get the data from Firestore and then call it from a handleSubmit function in login component
function getUserData() {
return db
.collection("customers")
.doc(currentUser.uid)
.get()
.then((doc) => {
setUserData(doc.data());
});
}
If I console log userData after calling the function I get undefined, but if I console.log(doc.data()), I get the correct object. Any hint on what I am doing wrong will be much appreciated.
This is the handleSubmit function where the function is being called in the login form component:
const onSubmit = async (values) => {
try {
setError("");
setLoading(true);
await login(values.email, values.password);
authenticateUser();
await getUserData();
console.log(userData);
history.push("/");
} catch {
setError("Failed to log in");
}
setLoading(false);
};```

I'm assuming setUserData is a useState hook function. If that's case, then it's an async function. That's why you are seeing data if you console log the doc.data(), but not userData.
If you want to wait until your userData exist before navigating to ('/') you can do the following. I would remove the await in front of getUserData because that's not doing anything.
useEffect(()=>{
if (user) history.push("/")
},[user]);

Any setState function is asynchronous so logging after it won't show values since it's still updating the state. To see an actual change you need an effect. Since userData is an object, regular useEffect will not work. What you need is something to watch changes in objects. That's where useDeepCompareEffect by Kent comes in.
import useDeepCompareEffect from 'use-deep-compare-effect'
...
const [userData, setUserData] = useState();
...
// This effect will do what you want when userData changes
useDeepCompareEffect(()=> {
if(userData) {
// do whatever you want here if userData is defined or whatever logic you looking for
history.push("/");
}
}, [userData])

Related

React order of execution

I have a code that sets a state of current user logged in, and i want to do something if the user is logged in, but the User information doesnt gets updated in order. I cannot explain it right, so ill right the code below.
const handleSubmit = async (event) => {
event.preventDefault()
try {
const user = await signInAuthUserWithEmailAndPassword(
email,
password
);
const test = await setCurrentUser(user)
console.log(currentUser)
currentUser ? setToggleSuccess(true) : setToggleSuccess(false)
resetFormFields();
console.log(currentUser)
} catch (error) {
the console.log's return 'null', but they are below the setCurrentUser. How to make the setCurrentUser get triggered before the console.logs?
setCurrentUser is definitely an async function but even if you add await while calling it, the state won't be updated instantaneously. You will get the changes in useEffect only. I will suggest you to use the variable user in the function defined instead of using "currentUser".
Also, if your resetFormFields uses currentUser then it should be called inside useEffect.
Note:- I am assuming that the current user is a react state and setCurrentUser is used to update the state.

ReactJS delay update in useState from axios response

I am new to react js and I am having a hard time figuring out how to prevent delay updating of use state from axios response
Here's my code:
First, I declared countUsername as useState
const [countUsername, setUsername] = useState(0);
Second, I created arrow function checking if the username is still available
const checkUser = () => {
RestaurantDataService.checkUsername(user.username)
.then(response => {
setUsername(response.data.length);
})
.catch(e => {
console.log(e);
})
}
So, every time I check the value of countUsername, it has delay like if I trigger the button and run checkUser(), the latest response.data.length won't save.
Scenario if I console.log() countUseranme
I entered username1(not available), the value of countUsername is still 0 because it has default value of 0 then when I trigger the function once again, then that will just be the time that the value will be replaced.
const saveUser = () => {
checkUser();
console.log(countUsername);
}
Is there anything that I have forgot to consider? Thank you
usually there is a delay for every api call, so for that you can consider an state like below:
const [loading,toggleLoading] = useState(false)
beside that you can change arrow function to be async like below:
const checking = async ()=>{
toggleLoading(true);
const res = await RestaurantDataService.checkUsername(user.username);
setUsername(response.data.length);
toggleLoading(false);
}
in the above function you can toggle loading state for spceifing checking state and disable button during that or shwoing spinner in it:
<button onClick={checking } disabled={loading}>Go
i hope this help
.then is not synchronous, it's more of a callback and will get called later when the api finishes. So your console log actually goes first most of the time before the state actually saves. That's not really something you control.
You can do an async / await and return the data if you need to use it right away before the state changes. And I believe the way state works is that it happens after the execution:
"State Updates May Be Asynchronous" so you can't really control when to use it because you can't make it wait.
In my experience you use the data right away from the service and update the state or create a useEffect, i.g., useEffect(() => {}, [user]), to update the page with state.
const checkUser = async () => {
try {
return await RestaurantDataService.checkUsername(user.username);
} catch(e) {
console.log(e);
}
}
const saveUser = async () => {
const user = await checkUser();
// do whatever you want with user
console.log(user);
}

Ho to wait with fetch until redux state is available?

I want to fetch some data from a database, and depending on the user the returned data should differ. The way i tried it, was by passing the userid as a query. The id is stored as a redux state. The problem is, that it takes some time before the redux state is available. Ive tried fixing this with if statements, and rerunning the useEffect everytime the auth state is updated. This doesn't work.
I want to fetch, when the redux state auth.user.id is available. Which it is like .1 sec after the initial load.
Here is my code:
const auth = useSelector((state) => state.auth);
useEffect(async () => {
if (auth.token.length > 0) {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
}
}, [auth, date]);
I believe useEffect is already asynchronous, so you don't need to use the async keyword in the anonymous callback. You can create the async function for that logic elsewhere and call it within the useEffect.
Similarly, you could put in self calling async function within your useEffect as such:
useEffect(() => {
(async () => {
if (auth.token.length) {
try {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
}catch (err) {console.log(err);}
}
})();
}, [auth, date]);
I think this link may be helpful:
React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing
So with the basic understanding, I assume that you need to call the API whenever userId is available. try the below useEffect
useEffect(async () => {
// check user id is available here
if (auth.user && auth.user.id) {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
// some other statements
}
}, [auth, date]);

Function inside useEffect fire twice even with empty dependency array

I have this example from https://github.com/vercel/next.js/blob/canary/examples/with-firebase-authentication/utils/auth/useUser.js
The effect works fine (fires once) but for some reason, the functions inside are called twice.
useEffect(() => {
const cancelAuthListener = firebase
.auth()
.onIdTokenChanged(async (user) => {
console.log('once or twice?')
if (user) {
// This fires twice
const userData = await mapUserData(user)
setUserCookie(userData)
setUser(userData)
} else {
removeUserCookie()
setUser()
}
})
const userFromCookie = getUserFromCookie()
if (!userFromCookie) {
router.push('/')
return
}
setUser(userFromCookie)
console.log(' i fire once')
return () => {
console.log('clean up')
cancelAuthListener()
}
}, [])
How can I make it to fire once?
I added some console logs:
On the first render I get: 'i fire once', 'once or twice', 'once or twice'
If I leave the page the cleanup console log fires (as it's supposed to do)
Many thanks
Later edit:
this is the code
export const mapUserData = async (user) => {
const { uid, email } = user
const token = await user.getIdToken()
return {
id: uid,
email,
token
}
}
If getIdToken() gets 'true' as an argument it will force a refresh regardless of token expiration.
https://firebase.google.com/docs/reference/js/firebase.User#getidtoken
Solved!!
the user was calling getIdToken(true) which forces a refresh.
https://firebase.google.com/docs/reference/js/firebase.User#getidtoken
Sorry guys, my bad!!!
You have a setState() inside useEffect thats the culprit, where useEffect having empty params [], one request on initial mount and another when do
setUser(userData) the component re-renders and useEffect() is invoked again.
Instead of using user as state, try using as ref and check. That might resolve this.

running a useEffect after a click even if the url trigger isn't changed

I'm following the nice article "How to fetch data with React Hooks?", in particular the section "ERROR HANDLING WITH REACT HOOKS".
There we have a useEffect that depends on the url where we fetch data from.
fetchData();
}, [url]);
The url is set on a submit from an input, specifying the query string.
<form
onSubmit={() =>
setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
}
>
My problem is that, even if the author defines
The error is just another state initialized with a state hook
and he implements the try/catch block for error handling and he finally concludes:
The error state is reset every time the hook runs again. That's useful
because after a failed request the user may want to try it again which
should reset the error.
actually I see that the useEffect to fetch data depends only on the url, that is the query string, hence, if the user doesn't change the query string, he can't try again. This can be useful especially to try after an error, but even more in general. How to achieve this goal?
I've tried
fetchData();
}, [url, isError]);
but it gets stuck in a loop of updates...
I'd add a boolean in state to track when to execute the fetchData()
const [loadContent, setLoadContent] = useState(true);
useEffect would look like this
useEffect(() => {
const fetchData = async () => {
setLoadContent(false) // Resetting the flag
setIsError(false);
setIsLoading(true);
try {
const result = await axios(`${url}${query}`);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
loadContent && fetchData() // Fetch data only if the flag is true
});
And finally form element like this
<form onSubmit={event => {
setLoadContent(true);
event.preventDefault();
}}>
Codesandbox link
My solution for this problem was
to add another hook state (representing the need to repeat the data fetch):
const [repeat, setRepeat] = useState(false);
to make the useEffect dependent on it (also adding a cleanup):
fetchData();
return function cleanup() {
setRepeat(false);
};
}, [url, repeat]);
and finally adding a check in the submit to force the data fetch even if the query string wasn't changed:
<form onSubmit={event => {
const before = url;
setUrl(algoliaUrl(query));
if (url === before)
{
setRepeat(true);
}
event.preventDefault(); }}>

Resources