1

I have a simple form with a couple text inputs. One is required, one is not. I have a custom input that adds to a list every time the user hits enter and is required. When the user submits the form, I check that all required fields have a value.

If I have entered a value for all required fields and hit enter, I will get a message that the form is invalid. If I hit enter one or two more times, I will get a finally get a message that my form is valid.

When I console.log the values of prioritiesArrayValid, demoFieldTwoValid, and formValid on form submit, they will all be their default value of false until I hit enter a few times

I know my question is a common one. I know it has something to do with setState and async but I can't see the issue.

Below is the code that run when the user hits enter.

validateField(fieldName, value) {
    let fieldValidationErrors = this.state.formErrors;
    let prioritiesArrayValid = this.state.prioritiesArrayValid;
    let demoFieldTwoValid = this.state.demoFieldTwoValid;
    let prioritiesArray = this.state.prioritiesArray;
    let demoFieldTwo = this.state.demoFieldTwo;

    switch (fieldName) {
      case "prioritiesArray":
        prioritiesArrayValid = value.length > 0;

        fieldValidationErrors.prioritiesArray = prioritiesArrayValid
          ? ""
          : " is required";
        this.setState(
          {
            prioritiesArray: prioritiesArray,
            prioritiesArrayValid: prioritiesArrayValid
          },
          () => {
            return {
              prioritiesArray: value,
              prioritiesArrayValid: prioritiesArrayValid
            };
          }
        );
        break;
      case "demoFieldTwo":
        demoFieldTwoValid = value.length > 0;

        fieldValidationErrors.demoFieldTwo = demoFieldTwoValid
          ? ""
          : " is required";
        console.log("...demoFieldTwo", demoFieldTwo);
        this.setState(
          { demoFieldTwo: demoFieldTwo, demoFieldTwoValid: demoFieldTwoValid },
          () => {
            return {
              demoFieldTwo: value,
              demoFieldTwoValid: prioritiesArrayValid
            };
          }
        );

        break;
      default:
        break;
    }

    this.setState(
      {
        formErrors: fieldValidationErrors
      },
      this.validateForm
    );
  }

  validateForm() {
    let formValid = this.state.formValid;
    this.setState({ formValid: formValid }, () => {
      return {
        formValid:
          this.state.prioritiesArrayValid && this.state.demoFieldTwoValid
      };
    });
    // console.log("this.state.formValid", this.state.formValid);
  }

  handleUserInput(e) {
    const name = e.target.name;
    const value = e.target.value;
    localStorage.setItem(name, value);
    this.setState({ [name]: value });
  }

  validateFields() {
    document
      .getElementById("form")
      .querySelectorAll("[required]")
      .forEach(el => {
        this.validateField(el.name, el.value);
      });
  }

  submitForm(e) {
    e.preventDefault();

    this.validateFields();
    if (this.state.formValid) {
      console.log("FORM VALID");
    } else {
      console.log("this.state.formErrors", this.state.formErrors);
    }
  }
ss_matches
  • 497
  • 2
  • 5
  • 20
  • You should use `callback` correctly. I am sure that it will help you. https://stackoverflow.com/questions/42038590/when-to-use-react-setstate-callback – Diamond Jul 29 '19 at 16:09
  • I agree. Could you elaborate please? – ss_matches Jul 29 '19 at 16:49
  • `this.setState({},()=>{ *call function here* })` – Jits Jul 29 '19 at 17:42
  • 1
    I guess if it was a bear it would bite my head off, but I just don't see it. I am using callbacks (possibly incorrectly) all over the place. I'm really at a loss – ss_matches Jul 29 '19 at 18:18

1 Answers1

1

It would be great if you had a sandbox or something of the sort, so we could see the full scope of what you are dealing with, not just the problem code.

From what I can gather, you're using state too much, and are expecting to receive the current state after setting it to determine if a form should be submitted or not. That's not really the ideal way you want to do this.

Your validateFields() call is synchronous, so validate each field without using state, then once you get all of your validations, then set state with whatever errors you have, and render based on that.

So kind of like this:

  /**
   *  Returns true if all fields validate, false if not.
   *
   *  @invokes this.setState() - state is set with whatever validation errors are found
   */
  validateFields() {
    const errors = [];

    const elems = document.querySelectorAll("#form [required]")
      .forEach(el => {
        const error = this.validateField(el.name, el.value);
        if (error) {
          errors.push(error);
        }
      });

    if (errors.length > 0) {
      this.setState({}); // <-- this should be your only set state? Set whatever you have to here, and let your app display your errors based on this.
      return false;
    } else {
      return true;
    }
  }

  submitForm(e) {
    e.preventDefault();

    if (this.validateFields()) {
      console.log("FORM VALID"); // Proceed to process the form!
    } else {
      console.log("this.state.formErrors", this.state.formErrors);
    }
  }

Like I said if you could provide a sandbox or something I can fork I could probably demonstrate better what I mean.

I hope this helps though!

Jimmay
  • 582
  • 5
  • 10