Состояние обновления useState недоступно в том же обработчике даже с тайм-аутом

У меня простая форма регистрации с 3 полями. Я сохранил состояние в formValues ​​с value & error, связанным с каждым полем. Теперь, когда я отправляю форму без заполнения какого-либо или хотя бы одного поля, форма должна быть недействительной, но вместо этого она показывает сообщения проверки с недопустимыми полями, но делает форму действительной. Даже если я добавил setTimeout, обновленное состояние недоступно в том же handleSubmit. Если я отправлю еще раз, процесс будет работать нормально. Я понимаю, что обновление состояния является асинхронным, но если мы видим журналы в консоли, сообщение проверки формы регистрируется после formValues log в рендере, и эти журналы показывают, что состояние было обновлено правильно, но окончательное сообщение проверки показывает недопустимое состояние. Если я изменю его на компонент класса, он будет работать. Вот ссылка на codeandbox.

import React, { useState } from "react";
import { Button, Form, Col } from "react-bootstrap";

const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));

const RegistrationForm = () => {
  const [formValues, setFormValues] = useState({
    name: { value: "", error: null },
    email: { value: "", error: null },
    password: { value: "", error: null }
  });

  const handleInputChange = (e, field) => {
    const { value } = e.target;
    setFormValues(prevValues => ({
      ...prevValues,
      [field]: { value, error: null }
    }));
  };

  const validateForm = () => {
    let updatedFormValues = { ...formValues };

    Object.keys(formValues).forEach(field => {
      if (!formValues[field].value) {
        updatedFormValues = {
          ...updatedFormValues,
          [field]: { ...updatedFormValues[field], error: "required" }
        };
      }
    });

    setFormValues(updatedFormValues);
  };

  const isFormValid = () =>
    Object.keys(formValues).every(field => formValues[field].error === null);

  const handleSubmit = async e => {
    e.preventDefault();

    validateForm();

    await sleep(100);

    if (!isFormValid()) {
      console.log("form is not valid", formValues);
      return;
    }

    console.log("form is valid", formValues);

    // make api call to complete registration
  };

  console.log({ formValues });

  return (
    <Form className="registration-form" onSubmit={handleSubmit}>
      <Form.Row>
        <Col>
          <Form.Group controlId="name">
            <Form.Label>Name</Form.Label>
            <Form.Control
              type="text"
              placeholder="Enter name"
              value={formValues.name.value}
              onChange={e => handleInputChange(e, "name")}
            />
            <Form.Control.Feedback type="invalid" className="d-block">
              {formValues.name.error}
            </Form.Control.Feedback>
          </Form.Group>
        </Col>
        <Col>
          <Form.Group controlId="email">
            <Form.Label>Email</Form.Label>
            <Form.Control
              type="email"
              placeholder="Enter email"
              value={formValues.email.value}
              onChange={e => handleInputChange(e, "email")}
            />
            <Form.Control.Feedback type="invalid" className="d-block">
              {formValues.email.error}
            </Form.Control.Feedback>
          </Form.Group>
        </Col>
      </Form.Row>
      <Form.Row>
        <Col>
          <Form.Group controlId="password">
            <Form.Label>Password</Form.Label>
            <Form.Control
              type="password"
              placeholder="Enter password"
              value={formValues.password.value}
              onChange={e => handleInputChange(e, "password")}
            />
            <Form.Control.Feedback type="invalid" className="d-block">
              {formValues.password.error}
            </Form.Control.Feedback>
          </Form.Group>
        </Col>
        <Col />
      </Form.Row>
      <Button variant="primary" type="submit">
        Submit
      </Button>
    </Form>
  );
};

export default RegistrationForm;


person Kamran Arshad    schedule 07.06.2020    source источник


Ответы (1)


Обновления состояния не только асинхронны, но и зависят от закрытия функциональных компонентов, поэтому использование спящего режима или тайм-аута не оставит вас с обновленным значением в том же цикле рендеринга.

Вы можете прочитать об этом больше в этом посте:

метод useState не сразу отражает изменение

Однако одно из решений в вашем случае - поддерживать ref, а toggle is value запускает useEffect, в котором вы проверяете обработчик post handleSubmit формы, проверяет его и устанавливает formValues

Соответствующий код:

const validateFormField = useRef(false);

  const handleInputChange = (e, field) => {
    const { value } = e.target;
    setFormValues(prevValues => ({
      ...prevValues,
      [field]: { value, error: null }
    }));
  };

  const validateForm = () => {
    let updatedFormValues = { ...formValues };

    Object.keys(formValues).forEach(field => {
      if (!formValues[field].value) {
        updatedFormValues = {
          ...updatedFormValues,
          [field]: { ...updatedFormValues[field], error: "required" }
        };
      }
    });

    setFormValues(updatedFormValues);
    validateFormField.current = !validateFormField.current;
  };

  const isFormValid = () =>
    Object.keys(formValues).every(field => formValues[field].error === null);

  const handleSubmit = async e => {
    e.preventDefault();

    validateForm();

    // make api call to complete registratin
  };

  useEffect(() => {
    if (!isFormValid()) {
      console.log("form is not valid", formValues);
    } else {
      console.log("form is valid", formValues);
    }
  }, [validateFormField.current]); // This is fine since we know setFormValues will trigger a re-render

Рабочая демонстрация

person Shubham Khatri    schedule 07.06.2020
comment
Спасибо за ссылку и ответ с подробным объяснением. Однако есть небольшая проблема с вашим кодом: когда компонент сначала монтируется, он делает форму действительной, поэтому это следует исправить, добавив что-то вроде isSubmitting в useEffect. - person Kamran Arshad; 08.06.2020