I am building a user login system and currently the way I have it set up, the user types in their email and password and then that information is sent to my server to be checked if accurate. If it is, I create a JWT token and send it back along with the mongodb _id of the user, I then in another request use the mongodb _id of the user from the first request to retrieve all the information (blogs in this case) associated with their mognodb _id. All of this works except for the issue I am running into is the axios request does not work the first time. So I have to hit submit to log in for an error to occur TypeError: Cannot read properties of undefined (reading 'data')
and then when I hit submit again it works. I believe the problem is getBasic
not getting set before the rest of the code runs on the first attempt, and on the second attempt it is already defined. I am not sure how I can fix this without maybe a manual timer before I do the getBasic.data.map
, but I know there must be a better way to fix this.
And just to add this is just me trying to test this concept so I understand later things like bcrypt will need to be added to the password instead of plaintext in the database, etc.
Frontend:
function Login() {
const [user, setUser] = useState()
const [getBasic, setGetBasic] = useState()
const [Item, setItem] = useState({
email: '',
password: ''
});
async function fetchData() {
const sentItem = {
user: user.data.user._id,
}
await axios.post('http://localhost:3001/api', sentItem)
.then(result => setGetBasic(result))
}
const loginFunc = async () => {
const checkUser = {
email: Item.email,
password: Item.password
}
await axios.post('http://localhost:3001/api/login', checkUser)
.then(result => setUser(result))
.then(fetchData())
}
if (user && getBasic) {
localStorage.setItem('key', JSON.stringify(user.data.token))
console.log(localStorage.getItem('key'))
return (
<div>
<div>
<input onChange={handleChange} name="email" value={Item.email} placeholder="email" />
<input onChange={handleChange} name="password" value={Item.password} placeholder="password" />
<button onClick={loginFunc}>Submit</button>
</div>
{
getBasic.data.map(({ text, _id, }, i) => {
return (
<div>
<p>{_id}</p>
<p>{text}</p>
</div>
);
})
}
)
}
return (
<div>
<input onChange={handleChange} name="email" value={Item.email} placeholder="email" />
<input onChange={handleChange} name="password" value={Item.password} placeholder="password" />
<button onClick={loginFunc}>Submit</button>
</div>
)
}
export default Login
Server:
app.post('/api/login', async (req, res) => {
const user1 = await User.findOne({ email: req.body.email, password: req.body.password })
if (user1) {
const payload = { name: req.body.name, email: req.body.email };
const token = jwt.sign(payload, private_key, { expiresIn: '200s' });
console.log('correct login')
res.status(200).send({ auth: true, token: true, user: user1 })
} else {
console.log('incorrect login')
}
})
app.post('/api', async (req, res) => {
Blog.find({ user: req.body.user }, function (err, result) {
if (err) {
console.log(err)
} else {
res.send(result)
}
})
})
3
Answers
While nature of
React.useState
is asynchronous, and changes will not be reflected immediately, closures take their role in situations like this, as pointed out in link in below.More details provided here – The useState set method is not reflecting a change immediately
As stated in error from your example, cannot read
data
of undefined – meaninguser
is undefined, and you try to access touser.data
which leads to error.Instead of
why don’t you try this (remove fetchData function if not reused on other places…):
I think you should create user state like this
const [user, setUser] = useState({})
.then(fetchData())
The
then
method accepts a function which runs when the other stuff is done. However, you are not passing in a function here, but the return result from calling fetchData.Try
.then(fetchData)
or
.then(() => fetchData())