useContext not updating on the same render - reactjs

I have got an issue where I need to update the name in my useContext and then straight away check it against another part of my code. Although when I check it against another part the name value in the context is still empty.
So this is my context file...
export const UserContext = createContext();
function UserContextProvider(props) {
const [user, setUser] = useState({
name: '',
email: ''
});
function updateUser(field, value) {
return new Promise((resolve, reject) => {
setUser((prevValue) => {
return {...prevValue, [field]: value}
})
resolve();
})
}
return (
<UserContext.Provider value={{user, updateUser}}>
{props.children}
</UserContext.Provider>
)
}
And this is what I want to run...
const { user, updateUser } = useContext(UserContext);
async function method(event) {
event.preventDefault();
await updateUser("name", "Shaun")
console.log(user);
}
I also tried with async and await but that didn't work either
I have re-produce the issue in my codesandbox here: https://codesandbox.io/s/youthful-smoke-fgwlr?file=/src/userContext.js

await in await updateUser("name", "Shaun") is just wait for the Promise at return new Promise((resolve, reject) didn't await for setUser, setUser is asynchronous function (not a Promise). So if you want to check get latest user you need to check it in another useEffect in App.js
useEffect(() => {
console.log(user);
}, [user]);

Simply put: you can't do it the way you think. Note that user is a const. Calling setUser within updateUser does not change the fact that user is still bound to the same value it was before. At no point during this execution of your component's render function is user being re-assigned (and if it were, you'd get an error because it is a const). user will not change until the next time your component is refreshed and useState is called again (which will be very shortly, considering you just called setUser).
That said, I'm not sure what use case would actually make sense to check it after calling setUser. You already know that the user's name should be Shaun, so if you need that value for something, you already have it; checking that it has been assigned to user isn't necessary or useful at all. If you expect your updateUser function to potentially make changes to the value you pass, and you need to validate that the value is correct, you should have your async function return the value. Then you can check that value after the promise resolves. That said, any check like that probably should exist within your updateUser function anyways; any attempt to check or validate it after calling setUser seems like an anti-pattern.

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);
}

React hook for logging in anonymously if no other login method

I want to have my users log in automatically anonymously. That's not too difficult to do. However I don't want anonymous logins to override their account logins. That's where I am running into trouble. I can't seem to find the way to do this.
Here is my hook:
import React, { useEffect, useState } from 'react';
import { singletonHook } from 'react-singleton-hook';
import { useAuth, useUser } from 'reactfire';
function SignInAnonymously() {
const auth = useAuth();
const user = useUser();
useEffect(() => {
user.firstValuePromise.then(() => {
if (!user.data) {
auth.signInAnonymously().then(() => {
console.log('Signed in anonymously');
}).catch((e) => console.log(e));
}
});
}, [user.firstValuePromise, user.data]);
return <></>;
}
export default singletonHook(<></>, SignInAnonymously);
The idea is that we get the first value emitted and compare that to the data object. However, it does not work as I would expect. The value emitted even for someone that was signed in returns null. If I comment the hook the user stays logged into their account. I have spent hours on this trying all the properties of the user so any help is appreciated.
Inside the useUser() method of reactfire, they use firebase.auth().currentUser as the initial value of the observable as seen on this line.
As covered in the Firebase Authentication docs:
Note: currentUser might also be null because the auth object has not finished initializing. If you use an observer to keep track of the user's sign-in status, you don't need to handle this case.
By reactfire setting the initial value to currentUser, you will often incorrectly get the first value as null (which means firstValuePromise will also resolve as null) because Firebase Auth hasn't finished initializing yet.
To suppress this behaviour, we need specify a value for initialData to pass in to useUser. I'd love to be able to use undefined, but thanks to this truthy check, we can't do that. So we need some truthy value that we can ignore such as "loading".
Applying this to your component gives:
/**
* A truthy value to use as the initial value for `user` when
* `reactfire` incorrectly tries to set it to a `null` value
* from a still-initializing `auth.currentUser` value.
*
* #see https://stackoverflow.com/questions/67683276
*/
const USER_LOADING_PLACEHOLDER = "loading";
function SignInAnonymously() {
const auth = useAuth();
const user = useUser({ initialData: USER_LOADING_PLACEHOLDER });
useEffect(() => {
if (user.data !== null)
return; // is still loading and/or already signed in
auth.signInAnonymously()
.then(() => console.log('Signed in anonymously'))
.catch((e) => console.error('Anonymous sign in failed: ', e));
}, [user.data]);
return <></>;
}

State Keeps Refreshing Hundreds of Times, Even With useEffect

So I am following Brad Traversy's React course, but it's a little old and I am using hooks and modern React for my own project, but I have a problem. When I enter the Detail page for a user, it calls a function to get the information of that user from an api. However, it turns out I am calling this function hundreds of times, even with useEffect. I will share my code, but I am not sure what I am doing wrong! If anyone can advise me on why this is re-rendering thousands of times, it would definitely be helpful. Thank you!
This is the function that is being called by Detail (setLoading is a useState function, as well as setSingleUser.
const getUser = username => {
setLoading(true);
axios
.get(
`https://api.github.com/users/${username}?client_id=
${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(res => {
axios
.get(
`https://api.github.com/users/${username}/repos?per_page=5&sort=created:asc&
client_id=${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(repores =>
setSingleUser({ ...res.data, repos: repores.data }),
);
});
setLoading(false);
};
This is where I call it in Detail
const Detail = ({ getUser, user, loading }) => {
const { username } = useParams();
useEffect(() => {
getUser(username);
}, [username, getUser]);
I have also tried useEffect with an empty dependency array and no dependency array, but it does not like any of these options.
As per your question, you seem to be defining getUser directly within the Parent component and passing it to child component.
Since you update state within the getUser function, the parent component will re-render and a new reference of getUser will be created. Now that you are passing getUser as a dependency to useEffect the useEffect runs again as getUser function has changed.
To solve this, you must memoize the getUser function in parent and you can do that using the useCallback hook
const getUser = useCallback(username => {
setLoading(true);
axios
.get(
`https://api.github.com/users/${username}?client_id=
${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(res => {
axios
.get(
`https://api.github.com/users/${username}/repos?per_page=5&sort=created:asc&
client_id=${BLAHBLAH}&client_secret=
${BLAHBLAH}`,
)
.then(repores =>
setSingleUser({ ...res.data, repos: repores.data }),
);
});
setLoading(false);
}, []);
Once you do that your code should work fine
Just include the username in the dependency array. Remove the function getUser since when the username is changed, the return is executed, again when getUser is called, username changes and again return is executed.
Try using this code:
useEffect(() => {
getUser(username);
}, [username]);
This is happening because of the second parameter to useEffect [username, getUser]. The array of dependencies for which the useEffect must be called is being called within your useEffect directly, which then triggers the useEffect again. So, The getUser is called again. Which then calls useEffect again. And therefore its stuck in a loop. You are pretty much calling a function (getUser) again inside of a function (useEffect) that you want to call each time (getUser) is called.
You need to add an if condition in the useEffect which makes sure the getUser() function gets called only when username is false or null or undefined, or you can also change the array that you are passing to the useEffect. You can even take advantage of the setSingleUser() that you have in the .then(). And can add the values of those in the if check or else in the array.

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

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])

Resources