React order of execution - reactjs

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.

Related

Supabase onAuthStateChange() triggers when switching tabs in React

I have the following code in my React project using Supabase:
// supabaseClient.ts
export const onAuthStateChangedListener = (callback) => {
supabase.auth.onAuthStateChange(callback);
};
// inside user context
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((event, session) => {
console.log(event);
});
return unsubscribe;
}, []);
However, every time I switch tabs away from the tab rendering the website to something else, and back, I see a new log from this listener, even if literally no change happened on the website.
Does anyone know the reason for this? The useEffect inside my user context component is the only place in my app where the listener is being called. To test, I wrote this dummy function inside my supabaseClient.ts file:
const testFunction = async () => {
supabase.auth.onAuthStateChange(() => {
console.log("auth state has changed");
});
};
testFunction()
This function also renders every time I switch tabs. This makes it a little annoying because my components that are related to userContext re render every time a tab is switched, so if a user is trying to update their profile data or something, they cannot switch tabs away in the middle of editing their data.
Supabase onAuthStateChange by default triggers every time a tab is switched. To prevent this, when initializing the client, add {multiTab: false} as a parameter.
Example:
const supabase = createClient(supabaseUrl, supabaseAnonKey, {multiTab: false,});
Here is my solution to the same problem. The way I've found is saving the access token value in a cookie every time the session changes, and retrieve it when onAuthStateChange get triggered, so I can decide to not update anything if the session access token is the same.
// >> Subscribe to auth state changes
useEffect(() => {
let subscription: Subscription
async function run() {
subscription = Supabase.auth.onAuthStateChange(async (event, newSession) => {
// get current token from manually saved cookie every time session changes
const currentAccessToken = await getCurrentAccessToken()
if (currentAccessToken != newSession?.access_token) {
console.log('<<< SUPABASE SESSION CHANGED >>>')
authStateChanged(event, newSession)
} else {
console.log('<<< SUPABASE SESSION NOT CHANGED >>>')
}
}).data.subscription
// ** Get the user's session on load
await me()
}
run()
return function cleanup() {
// will be called when the component unmounts
if (subscription) subscription.unsubscribe()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

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

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.

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

useContext not updating on the same render

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.

Resources