[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]);
Другие вопросы по тегам