Reputation: 195
Working on a login form / logout button with React/Redux front end and my own nodejs/express api. Having an issue with the login form. Most of the time it works just fine, but I'm getting erros on a regular basis. First error is forbidden
, which tells me that the user is not quite authenticated before send the userDetails
request.
Then there's another bug where Redux doesn't change the role of the user, which I need to dynamically render the nav. I'm thinking converting handleLogin to async/await will be the solution, but I believe I'm not doing it right.
import React from 'react';
import { login, userDetails } from '../axios/homeApi';
import { useForm } from 'react-hook-form';
import { useDispatch } from 'react-redux';
import { setLogin, setRole } from '../redux/actions';
const LoginForm = () => {
const { handleSubmit, register, errors } = useForm();
const dispatch = useDispatch();
const handleLogin = values => {
login(values.email, values.password)
.then(res => {
const token = res.data.token;
window.localStorage.setItem('auth', token);
dispatch(setLogin({ loggedIn: true }));
userDetails()
.then(res => {
const role = res.data.data.role;
dispatch (setRole({ role }));
})
})
}
return (
<div>
<form action="" onSubmit={handleSubmit(handleLogin)} className="footer-form">
<input
type="email"
placeholder="Enter Email Here"
name="email"
ref={register({ required: "Required Field" })}
/>
<input
type="password"
placeholder="Enter Password Here"
name="password"
ref={register({
required: "Required Field",
minLength: { value: 6, message: "Minimum Length: 6 Characters" }
})}
/>
{errors.password && errors.password.message}
{errors.email && errors.email.message}
<input type="submit" value="Login" />
</form>
</div>
)
}
export default LoginForm;
Here's my best attempt at converting handleLogin to async/await. I'm trying to understand how I'm supposed to pull data from these calls.
const handleLogin = async values => {
try {
const {data: {token}} = await login(values.email, values.password)
window.localStorage.setItem('auth', token);
console.log(token);
const user = await userDetails();
await dispatch(setLogin({ loggedIn: true}))
await dispatch(setRole(user.data.data.role))
} catch(err) {
console.log(err)
}
}
Any help/guidance on this would be greatly appreciated.
Upvotes: 4
Views: 4481
Reputation: 8150
Consider any object with a then
property, which is a function that accepts a callback as its 1st parameter, for example:
let obj = {
then: callback => callback('hello')
};
await
converts any such object into the value then
provides to the callback. Therefore:
(await obj) === 'hello'
In your example there are two instances where you require the value returned to a then
callback:
login(...).then(res => { /* got res */ });
and
userDetails().then(res => { /* got res */ });
Think of await
simply as a way of getting the value returned to an object's then
callback! In this case the objects are the result of login(...)
and userDetails()
, and you can convert to:
let res = await login(...);
and
let res = await userDetails();
You can see this also saves a bunch of indentation, one of many reasons people enjoy using async
/ await
!
These conversions from the then
-callback-value to await
, when inserted into your code, look like:
const handleLogin = async values => {
let loginRes = await login(values.email, values.password);
let token = loginRes.data.token;
window.localStorage.setItem('auth', token);
dispatch(setLogin({ loggedIn: true }));
let userDetailsRes = await userDetails();
let role = userDetailsRes.data.data.role;
dispatch(setRole({ role }));
};
Note that the function must be marked async
, and that I've renamed res
to a more specific name, since both responses now exist in the exact same scope, and need to be differentiated from each other.
Overall whenever you use then
to get ahold of some value in a callback, you are able to convert to the more elegant await
equivalent.
Upvotes: 1
Reputation: 74
Looks like you may have already have an answer, but it looks to me like it may be because you are not waiting on your dispatch of setLogin to complete. I don't know how your setLogin method is setup, but it would have to be a thunk. I found the following post that explains it well.
How to return a promise from an action using thunk and useDispatch (react-redux hooks)?
Upvotes: 4
Reputation: 15187
You have to think when you use await
, the variable value is the same that returned into res
without await
.
So if you have:
login(values.email, values.password)
.then(res => {
})
This is like:
var login = await login(values.email, values.password);
So using this logic, this:
login(values.email, values.password)
.then(res => {
const token = res.data.token;
// whatever
userDetails()
.then(res => {
const role = res.data.data.role;
// whatever
})
})
Turn into:
var login = await login(values.email, values.password)
const token = login.data.token;
// do whatever
var userDetails = await userDetails()
const role = userDetails.data.data.role;
// whatever
Check how works this example. The code is "the same". One using .then
and the other using await
.
runThenFunction();
runAwaitFunction();
function runThenFunction(){
console.log("Enter then function")
this.returnPromise(2000).then(res => {
console.log(res);
this.returnPromise(1000).then(res => {
console.log(res);
});
});
//You can log here and it will be displayed before the promise has been resolved
console.log("Exit then function")
}
async function runAwaitFunction(){
console.log("Enter await function")
var firstStop = await returnPromise(1000);
console.log(firstStop)
var secondStop = await returnPromise(4000);
console.log(secondStop)
// Using await the code "stops" until promise is resolved
console.log("Exit await function")
}
function returnPromise(time){
return new Promise(resolve => setTimeout(() => resolve("hello: "+time+"ms later."), time));
}
Upvotes: 7