[React-Context]: ошибка проверки формы не появляется в первый раз
Ошибка проверки в моей форме входа отображается только при втором нажатии кнопки отправки с неверными учетными данными. Я замечаю, что, хотя состояние обновляется, сообщение об ошибке не отображается в первый раз. Чтобы увидеть сообщение об ошибке Snackbar, требуется второй щелчок по отправке.
Я определил отдельный auth-context, в котором есть вся логика для создания почтового запроса для входа в систему и выполнения обработки ошибок, проверки, а также для установки пользовательских данных в localStorage. У него также есть поставщики контекста для предоставления значения текущего состояния. Login.js использует этоauth-context
сделать почтовый запрос и в зависимости от состояния либо войти в систему, либо (как предполагается) отобразить ошибки проверки.
Вот часть моего компонента страницы Login.js:
import { useAuth } from '../../context/auth-context';
export default function Login() {
const classes = useStyles();
const { handleChange, handleSubmit, formValues, formErrors } = useForm(submit, validate);
const [errorAlertMsg, setErrorAlertMsg] = useState('');
const authState = useAuth();
function submit() {
authState.login(formValues);
if(authState.error){
setErrorAlertMsg(authState.error);
}
/*authState initially shows error as null instead of the validation error. After submit is clicked it stays that way upon reaching here. On 2nd submit click it shows*/
console.log(authState);
}
function closeAlertHandler() {
setErrorAlertMsg('');
}
function Alert(props) {
return <MuiAlert elevation={6} variant="filled" {...props} />;
}
return (
<div>
<Grid container>
<Grid
item xs={4}
direction='row'
>
<UnauthenticatedSidebar/>
</Grid>
<Grid
item xs={8}
direction='row' >
<Container className={classes.mainContainer} component="main" maxWidth="xs">
<CssBaseline />
<Grid container justify='flex-end'>
<Grid item>
<Typography classes = {{ body2: classes.body2Style }} component="p" variant="body2">
Don't have an account?
<Link className={classes.routerLink} to='/signup'>
<Button color='primary' classes = {{ root: classes.buttonMarginRight }} variant="outlined" size="large">
Create Account
</Button>
</Link>
</Typography>
</Grid>
</Grid>
<div className={classes.paper}>
<Typography className={classes.welcomeHeading} component="h2" variant="h4">
Welcome Back!
</Typography>
<form onSubmit= { handleSubmit } className={classes.form} noValidate>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
InputProps = {{
classes: {root: classes.textFieldSelectLabel}
}}
InputLabelProps = {{
classes: {root: classes.textFieldSelectLabel}
}}
value={ formValues.email }
onChange = { handleChange }
error = { formErrors.email }
helperText = { formErrors.email || null }
/>
</Grid>
<Grid item xs={12}>
<TextField
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
InputProps = {{
endAdornment: <InputAdornment position='end'>
<Typography className={classes.adornmentStyle}>
Forgot?
</Typography>
</InputAdornment>,
classes: {root: classes.textFieldSelectLabel}
}}
InputLabelProps = {{
classes: {root: classes.textFieldSelectLabel}
}}
value={formValues.password}
onChange= { handleChange }
error = { formErrors.password }
helperText= { formErrors.password || null}
/>
</Grid>
</Grid>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submit}
>
Login
</Button>
</form>
<Snackbar open = {errorAlertMsg.length !== 0} autoHideDuration={5000} onClose = { closeAlertHandler }>
<Alert onClose={closeAlertHandler} severity="error">
{errorAlertMsg}
</Alert>
</Snackbar>
</div>
</Container>
</Grid>
</Grid>
</div>
);
}
Вот файл auth-context.js
import React, { useState } from 'react';
import axios from 'axios';
const AuthContext = React.createContext([{}, () => {}]);
function AuthProvider({children}) {
const [state, setState] = React.useState({
status: 'logged out',
error: null,
user: null
});
const [token, setToken] = useState(localStorage.getItem('authToken') ? localStorage.getItem('authToken') : null);
const [email, setEmail] = useState(localStorage.getItem('email') ? localStorage.getItem('email'): null);
//checking for token and email and then accordingly updating the state
const getUser = () => {
if(token) {
setState({status: 'success', error: null, user: email})
}
}
const logout = () => {
localStorage.removeItem('authToken');
setToken(null);
localStorage.removeItem('email');
setEmail(null);
setState({status: 'logged out', error: null, user: null})
}
const login = async(formValues) => {
try {
const res = await axios.post('http://localhost:3001/user/login', formValues);
if(res.data.token) {
setState({status:'success', error:null, user: formValues.email});
localStorage.setItem('authToken', res.data.token);
setToken(res.data.token);
localStorage.setItem('email', res.data.email);
setEmail(res.data.email);
}
}
catch(err) {
const validationError = err.response.data.validationError || null;
const missingDataError = err.response.data.missingData || null;
if(validationError){
setState({status: 'error', error: validationError, user: null})
}
else if(missingDataError) {
setState({status: 'error', error: missingDataError, user: null})
}
else {
console.error(err);
}
}
}
React.useEffect(() => {
getUser();
}, [token, email]);
let authState = {...state, logout, login}
/**
* Provider component is the place where you'd pass a prop called value to,
* which you can subsequently consume within the Consumer component
*/
return (
<AuthContext.Provider value={authState}>
{state.status === 'pending' ? (
'Loading...'
) : state.status === 'logged out' ? (
children
) : (
children
)}
</AuthContext.Provider>
)
}
//this seems simpler method to pass functions from context to consumers
function useAuth() {
const context = React.useContext(AuthContext)
if (context === undefined) {
throw new Error(`useAuth must be used within a AuthProvider`)
}
return context;
}
export {AuthProvider, useAuth};
1 ответ
Здесь есть две проблемы
- логин - асинхронная функция
- Обновления состояния связаны с закрытием в функциональном компоненте и не отражаются в том же цикле рендеринга.
Поскольку вы используете authState.error сразу после вызова authState.login, вы не видите обновленное значение.
Решение состоит в том, чтобы использовать useEffect и подождать, пока authState изменится.
function submit() {
authState.login(formValues);
}
useEffect(() => {
if(authState.error){
setErrorAlertMsg(authState.error);
}
console.log(authState);
}, [authState]);