Omkar Rasal
Omkar Rasal

Reputation: 55

How to do form validation in react?

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

Upvotes: 1

Views: 2054

Answers (3)

Mohammad Oftadeh
Mohammad Oftadeh

Reputation: 1449

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>
 )}

Upvotes: 2

Junius L
Junius L

Reputation: 16122

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>

Upvotes: 1

Nathan
Nathan

Reputation: 499

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.

Upvotes: 0

Related Questions