I'm trying to sign in and setting the current user. The problem is that the login is successful, the data is correct but I can't set the state, the user is empty.
UserContext.js
import React, { useContext, useState } from 'react';
const UserContext = React.createContext();
export const useAuth = () => {
return useContext(UserContext);
}
export const UserContextProvider = ( {children} ) => {
const [ user, setUser ] = useState({
name: '',
lastname: '',
username: '',
password: ''
});
const [ validation, setValidation ] = useState({
username: '',
password: ''
});
const setUserData = (e) => {
return ( {target: {value}} ) => {
setUser(data => ( {...data, [e]: value} ));
}
}
const setUserValidation = (e) => {
return ( {target: {value}} ) => {
setValidation(data => ( {...data, [e]: value} ));
}
}
const signUp = async () => {
return await fetch('http://localhost:8080/users/signup', {
method: 'POST',
body: JSON.stringify(user),
headers: { 'Content-Type': 'application/json' }
});
}
const signIn = async () => {
return await fetch('http://localhost:8080/users/signin', {
method: 'POST',
body: JSON.stringify(validation),
headers: { 'Content-Type': 'application/json' }
}).then((res) => res.json())
.then(data => {
console.log(data);
setUser({
name: data.name,
lastname: data.lastname,
username: data.username,
password: data.password
});
console.log(user);
});
}
const signOut = async () => {
await fetch('http://localhost:8080/users/signout');
setUser(null);
return;
}
return (
<UserContext.Provider value={{
user,
setUserData,
setUserValidation,
signUp,
signIn,
signOut
}}>
{ children }
</UserContext.Provider>
);
}
SignIn.js
import './SignIn.css';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/UserContext';
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().then(() => {
console.log(user);
setErrorMessage(null);
navigate('/');
}).catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
return (
<div className='SignIn'>
<h3>Sign In</h3>
<form className='LoginForm' onSubmit={handleSubmit}>
{ errorMessage && <h4 className='ErrorMessage'>{errorMessage}</h4> }
<input type='email' name='username' placeholder='Email' onChange={setUserValidation('username')} required/>
<input type='password' name='password' placeholder='Password' onChange={setUserValidation('password')} required/>
<button className='Login' type='submit'>Sign In</button>
</form>
<h5>Don't have an account?</h5><Link className='Redirect' to='/signup'>Sign Up</Link>
</div>
);
}
export default SignIn;
CodePudding user response:
I guess what could be happening here. I have no time to test my guess, so please forgive me if I'm saying something wrong.
When you change a state in a context, the provider and its children are re-rendered. In this case, the UserContextProvider and its children.
So first thing, please be sure that SignIn is rendered inside a UserContextProvider, e.g. embedding all the app inside a . I generally do this in the index.js file.
ReactDOM.render(
<UserContextProvider>
{/* ... app here, that includes SignIn ... */}
</UserContextProvider>,
document.getElementById('root')
);
Second thing, since you are including the console.log() so that are executed in the same rendering in which you change the state, it's clear that they won't reflect the new value that will be available in the successive rendering only.
I suggest that you put the console.log(user) at the beginning of the SignIn component, say immediately after useNavigate(), outside the handleSubmit function.
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
console.log(user)
// ...ecc...
}
If I'm right, this console.log will be executed (at least) twice, one for the initial rendering, one for the subsequent rendering triggered by setUser (you can also include a console.log in the handleSubmit just to detect the re-rendering triggered by setUser). In the last rendering, you should see the user data.
If this works as I expect, I guess that you can handle the signIn with something like this
const SignIn = () => {
const { signIn, setUserValidation, user } = useAuth();
const [ errorMessage, setErrorMessage ] = useState(null);
const navigate = useNavigate();
// in the first rendering, the userName will be '', so it won't navigate
// if the component is re-rendered after the setUser in signIn,
// in this rendering there will be a userName, hence the navigation will proceed
if (user.userName !== '') {
navigate('/');
}
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
return (
// ... as before ...
);
}
Happy coding! - Carlos
CodePudding user response:
This is a normal behavior. You can't access state directly after set state. The updated state will only be available on next render. So do about context.
This is what you can do if you need to access user data directly after SignIn().
UserContext.js
const signIn = async () => {
return await fetch('http://localhost:8080/users/signin', {
method: 'POST',
body: JSON.stringify(validation),
headers: { 'Content-Type': 'application/json' }
}).then((res) => res.json())
.then(data => {
console.log(data);
const usr = {
name: data.name,
lastname: data.lastname,
username: data.username,
password: data.password
}
setUser(usr);
// console.log(user); <-- You can't do this
return usr // <--
});
}
SignIn.js
const handleSubmit = async (e) => {
e.preventDefault();
setErrorMessage(null);
signIn().then((usr) => { // <---
console.log(usr);
// console.log(user); <-- You can't do this
setErrorMessage(null);
navigate('/');
}).catch(err => {
setErrorMessage('Error singing in, please try again.', err);
});
}
