Home > database >  How to write proper form validation in react?
How to write proper form validation in react?

Time:11-07

I've been working on form validation in my application, the user must provide a proper company name - full name - phone and email address. The weird behaviours I am facing is that :

1 - when I submit without filling out any input, I got the error message for the phone even though it is not the first place input. 2 - If a full out the form without the company name it, accept it too!

import { React, useState, useEffect } from "react";
/*
  *****
  *****
  Import Showcase.scss - 
*/
import "./Showcase.scss";
const Showacase = () => {
  /*
  *****
  *****
  Logic Goes Here
*/

  //State Management

  //Company State
  *const [company, setCompany] = useState();
  **const [companyValue, setCompanyValue] = useState();**
  //name State
  **const [name, setName] = useState();**
  **const [nameValue, setNameValue] = useState();**
  //Phone State
  **const [phone, setPhone] = useState();
  **const [phoneValue, setPhoneValue] = useState();****
  //Email State
  **const [email, setEmail] = useState();**
  **const [emailValue, setEmailValue] = useState();**
  //Hide Inputs
  const [input, setInput] = useState(true);
  // Message State
  const [message, setMessage] = useState(true);
  // SubMessage
  const [subMessage, setSubMessage] = useState(true);
  //InsertLine
  const [line, setLine] = useState(false);
  //Hide Button
  const [btn, setBtn] = useState(true);
  /* Validation State */

  **const [companyValidate, setCompanyValidate] = useState(false);
  **const [nameValidate, setNameValidate] = useState(false);**
  **const [phoneValidation, setPhoneValidation] = useState(false);**
  **const [emailValidate, setEmailValidate] = useState(false);*****
  // OnSubmit
  const onSubmit = (e) => {
    e.preventDefault();
  };

  //onChange Company
  const onChangeCompany = (e) => {
    setCompany(e.target.value);
  };

  //OnClick
  const onClick = (e) => {
    // Company
    **const reCompanyName = /^[a-zA-Z](?:[^0-9\s\x00-\x1F]|[ \t]){1,79}$/;**
    //Name
    **const reName = /[A-Za-z]{1,50}/;**
    // Email Address
    **const reEmail =
      /^([a-zA-Z0-9_\-\.] )@([a-zA-Z0-9_\-\.] )\.([a-zA-Z]{2,5})$/;**
    //Phone number
    **const rePhone = /\ ?[0-9] ([0-9]|\/|\(|\)|\-| ){10,}/g;**

    **if (!reCompanyName.test(company)) {
      setCompanyValidate(true);
      return;
    } else {
      setCompanyValidate(false);
    }**

    **if (!reName.test(name)) {
      setNameValidate(true);
      return;
    } else {
      setNameValidate(false);
    }**

    **if (!rePhone.test(phone)) {
      setPhoneValidation(true);
      return;
    } else {
      setPhoneValidation(false);
    }**

    **if (!reEmail.test(email)) {
      setEmailValidate(true);
      return;
    } else {
      setEmailValidate(false);
    }**

    //upComing Phone

    **if (company === "" || phone === "" || email === "" || name === "") {
      return;
    } else {
      /* setting the New Values */
      setCompanyValue(company);
      setNameValue(name);
      setPhoneValue(phone);
      setEmailValue(email);
      /* Clearing the Inputs */
      setCompany("");
      setName("");
      setPhone("");
      setEmail("");
      /* Logic of hidding Items  */
      setInput(false);
      setMessage(false);
      setSubMessage(false);
      setLine(true);
      setBtn(false);
      /* Reset all options after submission after 5 seconds  */
      setTimeout(() => {
        setInput(!false);
        setMessage(!false);
        setSubMessage(!false);
        setLine(!true);
        setBtn(!false);

        setCompanyValidate(false);
        setNameValidate(false);
        setPhoneValidation(false);

        setEmailValidate(false);
      }, 5000);
    }
  };**

  return (
    <>
      {/* Showcase Main  */}
      <div className="Showcase">
        <div className="Container">
          {/* Showcase Main / Applying the flex */}
          <div className="Showcase__inside">
            {/* Right Side / form and logic  */}
            <div className="Showcase__inside--left">
              <div className="Showcase__inside--left--box">
                {/* the Top Message */}
                {message ? (
                  <h1>Find inbound call centers for your company</h1>
                ) : (
                  <h1>Thank you for your request!</h1>
                )}
                {/* the sub Message Message */}
                {subMessage ? (
                  <p className="paragraphWilBeRemovedOnSmallScreen">
                    Use our AI and Big Data driven call center sourcing solution
                  </p>
                ) : (
                  <p className="paragraphAfterSubmission">
                    You’ve taken the first step. Our experts will get in touch
                    with you soon.
                  </p>
                )}
                {/* Inserting the Line */}
                {line ? <hr></hr> : null}
                <form onSubmit={onSubmit}>
                  {/* Company  */}
                  <div className="Showcase__inside--left--box--form">
                    <label for="company">Company </label>
                    {input ? (
                      <div>
                        <input
                          required
                          value={company}
                          onChange={onChangeCompany}
                          type="text"
                          id="company"
                          placeholder="Company"
                          name="company"
                        />
                        {companyValidate ? (
                          <div className="companyValidate">
                            <small>Please provide a valid company name</small>
                          </div>
                        ) : (
                          ""
                        )}
                      </div>
                    ) : (
                      <p>{companyValue}</p>
                    )}
                  </div>
                  {/* Name  */}
                  <div className="Showcase__inside--left--box--form">
                    <label for="Name">Name </label>
                    {input ? (
                      <div>
                        <input
                          required
                          onChange={(e) => setName(e.target.value)}
                          value={name}
                          type="text"
                          id="Name"
                          placeholder="Full name"
                          name="Name"
                        />
                        {nameValidate ? (
                          <div className="nameValidate">
                            <small>Please Provide a valid Full Name</small>
                          </div>
                        ) : (
                          ""
                        )}
                      </div>
                    ) : (
                      <p>{nameValue}</p>
                    )}
                  </div>
                  {/* Phone  */}
                  <div className="Showcase__inside--left--box--form">
                    <label for="Phone">Phone </label>
                    {input ? (
                      <div>
                        <input
                          required
                          onChange={(e) => setPhone(e.target.value)}
                          value={phone}
                          type="number"
                          id="Phone"
                          placeholder=" 49"
                          name="phone"
                        />
                        {phoneValidation ? (
                          <div className="phoneValidation">
                            <small>Please Provide a valid Phone Number</small>
                          </div>
                        ) : (
                          ""
                        )}
                      </div>
                    ) : (
                      <p>{phoneValue}</p>
                    )}
                  </div>
                  {/* Email  */}
                  <div className="Showcase__inside--left--box--form">
                    <label for="Email">Email </label>
                    {input ? (
                      <div>
                        <input
                          required
                          onChange={(e) => setEmail(e.target.value)}
                          value={email}
                          type="email"
                          id="Email"
                          placeholder="[email protected]"
                          name="email"
                        />
                        {emailValidate ? (
                          <div className="EmailValidate">
                            <small>Please Provide a valid Email</small>
                          </div>
                        ) : (
                          ""
                        )}
                      </div>
                    ) : (
                      <p>{emailValue}</p>
                    )}
                  </div>
                  {/* Submit  */}
                  <div className="Showcase__inside--left--box--form">
                    <div className="Showcase__inside--left--box--form--submit">
                      {/* OnClick Method */}
                      {btn ? (
                        <button onClick={onClick} type="submit">
                          Get informed
                        </button>
                      ) : null}
                    </div>
                  </div>
                </form>
              </div>
            </div>
            {/* Right SIDE %s  */}
            <div className="Showcase__inside--right">
              <div>
                <div>
                  <h1>Welcome to Europe’s largest call center database </h1>
                </div>
                <div className="Showcase__inside--right--per">
                  <div className="Showcase__inside--right--per--single">
                    <small>500 </small>
                    <h1>Tenders</h1>
                  </div>
                  <div className="Showcase__inside--right--per--single">
                    <small>200 </small>
                    <h1>Callcenter</h1>
                  </div>
                  <div className="Showcase__inside--right--per--single">
                    <small>375.000</small>
                    <h1>Agents available</h1>
                  </div>
                  <div className="Showcase__inside--right--per--single">
                    <small>85%</small>
                    <h1>Faster sourcing</h1>
                  </div>
                </div>
              </div>
            </div>
            {/* Right Ended %s  */}
          </div>
        </div>
      </div>
    </>
  );
};
export default Showacase;

CodePudding user response:

I know this may be a bit off topic, but I suggest you to use a pattern for form building and validations. Especially those multiple useState can be a bit cumbersome to use.

What I tend to use in my code is generally something like this:

let [model, setModel] = useState({firstName: '', lastName: '', ...})
let validations = {
   firstName: ['required'], 
   ...
}

return <Form model={model} setModel={setModel} validations={validations}>
   <Field name="firstName" />
   <Field name="lastName" />
   <Submit>Send form</Submit>

</Form>

This is generally much shorter and helps consistency across your application.

If you want a sample implementation of form, fields, ..., you can use mine for instance:

import { cloneElement, useState } from "react";

export function Form({ model, setModel, validations, children, onSubmit, className }) {
  let [errors, setErrors] = useState({});

  function computeFieldErrors(fieldName, fieldValue) {
    let fieldValidations = validations[fieldName] || [];

    let fieldErrors = [];

    for (let validation of fieldValidations) {
      if (validation == "required") {
        if (!fieldValue) fieldErrors.push("Must not be empty");
      } else if (validation instanceof Array) {
        if (validation[0] == "matches") {
          if (fieldValue != model[validation[1]]) {
            fieldErrors.push("Does not match "   validation[1]);
          }
        } else {
          alert("unknown validation: "   validation[0]);
        }
      } else {
        alert("unknown validation: "   validation);
      }
    }

    return fieldErrors;
  }

  function validateForm() {
    let hasErrors = false;

    let res = { ...errors };

    for (let fieldName in model) {
      let fieldErrors = computeFieldErrors(fieldName, model[fieldName]);
      if (fieldErrors.length > 0) {
        hasErrors = true;
        res = { ...res, [fieldName]: fieldErrors };
      }
    }

    setErrors(res);

    if (!hasErrors) onSubmit(model);
  }

  function setData(fieldName, fieldValue) {
    setModel({ ...model, ...{ [fieldName]: fieldValue } });

    setErrors({ ...errors, [fieldName]: computeFieldErrors(fieldName, fieldValue) });
  }

  function fixChildren(nodes) {
    return nodes.map((child, i) => {
      if (child.type) {
        if (child.type.name == "Field") {
          return cloneElement(child, {
            key: i,
            data: model[child.props.name],
            errors: errors[child.props.name],
            setData: (value) => setData(child.props.name, value)
          });
        } else {
          return cloneElement(child, {
            key: i,
            children: child.props.children
              ? fixChildren(child.props.children instanceof Array ? child.props.children : [child.props.children])
              : null
          });
        }
      }
      else{
        return child;
      }
    });
  }

  return (
    <form
      className={'form '    className}
      onSubmit={(event) => {
        event.preventDefault();
        validateForm();
      }}
    >
      {fixChildren(children)}
    </form>
  );
}

export function Field({ type, name, label, data, setData, errors, component, disabled, placeholder }) {
  if (!component) {
    if (type == "textarea") {
      component = ({ data, setData }) => (
        <textarea
          id={name}
          value={data}
          onChange={(e) => setData(e.target.value)}
          disabled={disabled}
          defaultValue={data}
          placeholder={placeholder}
        ></textarea>
      );
    } else {
      component = ({ data, setData }) => (
        <input
          type={type || "text"}
          id={name}
          value={data}
          placeholder={placeholder}
          onChange={(e) => setData(e.target.value)}
          disabled={disabled}
        />
      );
    }
  }

  return (
    <div className={"field "   ((errors || []).length > 0 ? "field--witherrors" : "")}>
      <div className={"field__data"}>
        <div className={"field__label"}>
          <label htmlFor={name}>{label}</label>
        </div>
        <div className={"field__control"}>{component({ data, setData })}</div>
      </div>
      <div className={"field__errors"}>
        <span>{(errors || []).join(", ")}</span>
      </div>
    </div>
  );
}

export function Submit({children}) {
  return <button type={"submit"}>{children}</button>;
}

CodePudding user response:

I would recommend using a library such as formik or react-hooks-form (from npm).

Forms and their validation are a non-trivial part of web applications, and although you can write it yourself, I would recommend getting a helping hand from a well-tested library.

  • Related