1

I have created react form. I need help in form validation. I have created custom validations.

As seen in the code, I have 2 email fields in the same form. If I try to enter email in one field, the error shown for both email fields. The reason behind this is use of same 'invalidEmail' state property. I don't want to create separate state property for each field. Same case with age and phone fields.

Can anyone help me to solve this. You can suggest me different approaches if you have. I don't want to add any library or plugins for validations.

Codesandbox link : https://codesandbox.io/s/heuristic-bartik-1lucn

1
  • do want to show the error after trying to submit the form? Commented Feb 29, 2020 at 7:03

3 Answers 3

2

Notice: You can model this logic to develop, write better and best practice for all fields email, age, phone, ... in your react project

You can try this and edit your codesandbox:

Add new state : invalidFields for store invalid fields:

state = {
    user: {
      name: "",
      email1: "",
      email2: "",
      age: "",
      city: "",
      phone: ""
    },
    invalidNumber: false,
    invalidEmail: false,
    invalidFields: [], // Add this line
    submitted: false
  };

and then edit your conditions in handleChange function :

add name to this.validateEmail(value, name);

if (name === "email1") {
  this.validateEmail(value, name); // add name
}
if (name === "email2") {
  this.validateEmail(value, name); // add name
}
if(name === "age") {
  this.validateNumber(value, name); // add name
}
if(name === "phone") {
  this.validateNumber(value, name); // add name
}

and then change your validateEmail function : Edited

validateEmail = (value, name) => {
    const regex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
    if (value && !regex.test(value)) {
      // add invalidFields to this.setState 
      this.setState({ invalidEmail: true, invalidFields: [...this.state.invalidFields, name] });
    } else {
      // add invalidFields to this.setState and then filter
      this.setState({ invalidEmail: false, invalidFields: this.state.invalidFields.filter(item => item !== name) });
    }
  };

and finally change your condition for show error: Edited

// email1
{submitted && this.state.invalidFields.includes('email1') && (
    <div style={{ color: "red" }}>Email is invalid</div>
)}

// email2
{submitted && this.state.invalidFields.includes('email2') && (
    <div style={{ color: "red" }}>Email is invalid</div>
 )}
Sign up to request clarification or add additional context in comments.

6 Comments

It is still showing error message for both fields if I enter wrong input in one field.
Your problem was that the two email fields indicated one error for both if one of the emails was correct. This solution I told you was working properly and tested and to handle the rest you have to edit the code because you only mentioned this.
Can you please test it in my sandbox code and share me the correct code.
@OmkarRasal. I edited the validateEmail function and these // email1 {submitted && this.state.invalidFields.includes('email1') && ( <div style={{ color: "red" }}>Email is invalid</div> )} // email2 {submitted && this.state.invalidFields.includes('email2') && ( <div style={{ color: "red" }}>Email is invalid</div> )}. please edit your codesandbox with these.
This solution works for me. Thanks for your time @MohammadOftadeh
|
1

Add a new variable to state, which tracks if the required fields are valid. Use that variable to disable/enable the submit button.

state = {
 ...
  isValidForm: false
}

validateNumber = value => {
  const regex = /^[0-9]\d*$/;
  return regex.test(value);
};

validateEmail = email => {
  /* eslint-disable-next-line */
  const regexp = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
  return regexp.test(email);
};

handleChange = e => {
  const { name, value } = e.target;
  const { user } = this.state;

  this.setState(
    {
      user: {
        ...user,
        [name]: value
      }
    },
    () => {
      // update the value here
      this.setState({
        isValidForm:
          this.validateEmail(this.state.user.email1) &&
          this.validateEmail(this.state.user.email2) &&
          this.validateNumber(this.state.user.age) &&
          this.validateNumber(this.state.user.phone)
      });
    }
  );
};

Then disable/enable submit button based on the entered values.

<button disabled={!this.state.isValidForm} type="submit">
  Submit
</button>

To show errors while user is typing, add a variable in state which stores touched inputs, onFocus input add the focused input name to the array.

state = {
 ...
 isTouched: []
}

<div>
  <label htmlFor="email2">Email 2</label>
  <input
    type="email"
    id="email2"
    name="email2"
    onFocus={() => this.isTouched("email2")}
    value={user.email2}
    onChange={this.handleChange}
  />
  {submitted && !user.email2 && (
    <div style={{ color: "red" }}>Email 2 is required</div>
  )}
  {!this.validateEmail(this.state.user.email2) &&
    this.state.isTouched.includes("email2") && (
      <div style={{ color: "red" }}>Email is invalid</div>
    )}
</div>

.App {
  font-family: sans-serif;
  text-align: center;
}

div {
  margin: 0 0 10px 0;
  clear: both;
  overflow: hidden;
}
div label {
  float: left;
  width: 100px;
  text-align: left;
}
div input,
div select {
  float: left;
  width: 200px;
  padding: 5px;
  clear: both;
}
div div {
  text-align: left;
}
button {
  float: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<div id="root"></div>

<script type="text/babel">

class App extends React.Component {
  state = {
    user: {
      name: "",
      email1: "",
      email2: "",
      age: "",
      city: "",
      phone: ""
    },
    invalidNumber: false,
    invalidEmail: false,
    submitted: false,
    isValidForm: false,
    isTouched: []
  };

  validateNumber = value => {
    const regex = /^[0-9]\d*$/;
    return regex.test(value);
  };

  validateEmail = email => {
    /* eslint-disable-next-line */
    const regexp = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
    return regexp.test(email);
  };

  handleChange = e => {
    const { name, value } = e.target;
    const { user } = this.state;

    this.setState(
      {
        user: {
          ...user,
          [name]: value
        }
      },
      () => {
        this.setState({
          isValidForm:
            this.validateEmail(this.state.user.email1) &&
            this.validateEmail(this.state.user.email2) &&
            this.validateNumber(this.state.user.age) &&
            this.validateNumber(this.state.user.phone)
        });
      }
    );
  };

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

    const { name, age, email1, email2, city, phone } = this.state.user;
    this.setState({
      submitted: true
    });

    if (
      name !== "" &&
      age !== "" &&
      email1 !== "" &&
      email2 !== "" &&
      city !== "" &&
      phone !== ""
    ) {
      console.log("User created : ", this.state.user);
    } else {
      console.log("Error creating user");
    }
  };

  isTouched = inputName => {
    const inputs = [...this.state.isTouched];

    if (!inputs.includes(inputName)) {
      inputs.push(inputName);

      this.setState({
        isTouched: inputs
      });
    }
  };

  render() {
    const { submitted, invalidEmail, user } = this.state;
    return (
      <div className="App">
        <form onSubmit={this.handleSubmit}>
          <div>
            <label htmlFor="name">Name</label>
            <input
              type="text"
              id="name"
              onFocus={() => this.isTouched("name")}
              name="name"
              value={user.name}
              onChange={this.handleChange}
            />
            {!user.name && this.state.isTouched.includes("name") && (
              <div style={{ color: "red" }}>Name is required</div>
            )}
          </div>
          <div>
            <label htmlFor="age">Age</label>
            <input
              type="text"
              id="age"
              name="age"
              onFocus={() => this.isTouched("age")}
              value={user.age}
              onChange={this.handleChange}
            />
            {submitted && !user.age && (
              <div style={{ color: "red" }}>Age is required</div>
            )}
            {!this.validateNumber(user.age) &&
              this.state.isTouched.includes("age") && (
                <div style={{ color: "red" }}>Age must be numeric</div>
              )}
          </div>
          <div>
            <label htmlFor="email1">Email 1</label>
            <input
              type="email"
              id="email1"
              name="email1"
              onFocus={() => this.isTouched("email1")}
              value={user.email1}
              onChange={this.handleChange}
            />
            {submitted && !user.email1 && (
              <div style={{ color: "red" }}>Email 1 is required</div>
            )}
            {!this.validateEmail(this.state.user.email1) &&
              this.state.isTouched.includes("email1") && (
                <div style={{ color: "red" }}>Email is invalid</div>
              )}
          </div>
          <div>
            <label htmlFor="email2">Email 2</label>
            <input
              type="email"
              id="email2"
              name="email2"
              onFocus={() => this.isTouched("email2")}
              value={user.email2}
              onChange={this.handleChange}
            />
            {submitted && !user.email2 && (
              <div style={{ color: "red" }}>Email 2 is required</div>
            )}
            {!this.validateEmail(this.state.user.email2) &&
              this.state.isTouched.includes("email2") && (
                <div style={{ color: "red" }}>Email is invalid</div>
              )}
          </div>
          <div>
            <label htmlFor="phone">Phone</label>
            <input
              type="text"
              id="phone"
              name="phone"
              value={user.phone}
              onFocus={() => this.isTouched("phone")}
              onChange={this.handleChange}
            />
            {submitted && !user.phone && (
              <div style={{ color: "red" }}>Phone is required</div>
            )}
            {!this.validateNumber(user.phone) &&
              this.state.isTouched.includes("phone") && (
                <div style={{ color: "red" }}>Phone must be numeric</div>
              )}
          </div>
          <div>
            <label htmlFor="city">City</label>
            <select
              id="city"
              name="city"
              value={user.city}
              onChange={this.handleChange}
            >
              <option value="" disabled>
                Select
              </option>
              <option value="Delhi">Delhi</option>
              <option value="Mumbai">Mumbai</option>
              <option value="Pune">Pune</option>
            </select>
            {submitted && !user.city && (
              <div style={{ color: "red" }}>City is required</div>
            )}
          </div>
          <button disabled={!this.state.isValidForm} type="submit">
            Submit
          </button>
        </form>
      </div>
    );
  }
}

ReactDOM.render(
    <App />,
    document.getElementById('root')
);
</script>

6 Comments

Thanks, I will use this option to disable my button till invalid. But I want to show messages while entering wrong values.
Using this when the page loads it shows email error messages by default. When I combined it with submitted it shows both required and invalid email messages.
don't combine validation functions with submitted
Then it is showing email invalid errors when page loads. We need to show it when user touches field.
@OmkarRasal I've updaed my answer, I've added an example as well.
|
0

In my head, it seems to make sense to have two separate states. If it's just for the sake of not wanting to create another state I'd probably argue that readability and simplicity should be priority.

3 Comments

This is only demo, I have around 20 fields like this in my form. Is it feasible to create separate state for each field?
Maybe it would be more feasible to create a new component for each parameter? So have an <Email /> that handles its own state inside it. Then you can render it as much as you like and keep your code DRY.
Yes, that is possible. But I need to wrap complete form in the single component itself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.