In React Js, the form validation is a lengthy process, because of the need to check every State value which is definitely not a quick way to validate form.

But here is the simple step by step tutorial that will show you how to do basic form custom validation in React JS based on Class:

Let’s assume that we have created an App Called Demo Form.

Now, let’s create the Form component in src/DemoForm.js.

import React, {useState} from 'react'
const DemoForm = (props) => {
    const [state, setState] = useState({
        SimpleInput: '',
        SimpleEmail:'',
    });
    const handleChange = e => {
        setState(prevState => {
             return {
                  ...prevState,
                  [e.target.name]: e.target.value
             }
        });        
    }
return (<>
  <div className="page-content">               
    <section className="content">
      <form onSubmit={SubmitDemoFrm} id="myform" >
        <div className="card card-secondary">
          <div className="card-header">
               <h3 className="card-title"> Demo Form</h3>
          </div>
          <div className="card-body">
            <div className="row">
              <div className="col-3">
                <div className="form-group">
                  <label> Simple Required Input <span>*</span></label>
                  <input onChange={handleChange} type="text" className="form-control required" name="SimpleInput" value={state.SimpleInput} />
                </div>
              </div>
            </div>
            <div className="col-3">
              <div className="form-group">
                <label> Required Email <span>*</span></label>
                <input onChange={handleChange} type="text" className="form-control required email" name="SimpleEmail" value={state.SimpleEmail} />
              </div>
            </div>
          </div>
          <div className="card-footer">
            <input type="submit" className="btn btn-primary" value="Add" />
          </div>
        </div>
      </form>
    </section>
  </div>
 </>)
}
export default DemoForm;

Ok here is our simple form component.

Now let’s create our custom validation function..

const CustomValidation = (e, customeField = false) => {
    var isValidationStatus = true;
    if (customeField) {
        allFormElement.push(e.target);
    } else {
        for (let index = 0; index < e.target.length; index++) {
             allFormElement.push(e.target[index]);
        }
    }
    var inputs = Array.prototype.filter.call(
        allFormElement,
        function (element) {
             if (element.classList.contains("required")) {
                  return element.nodeName === "INPUT" || element.nodeName === "TEXTAREA";
             }
        }
    );
    var uniqueArray = [
        ...new Map(inputs.map((item) => [item["name"], item])).values(),
    ];    
}

In above code, we have taken all inputs and text areas that have “required” class, so we can validate that in bulk.

Now, we need to add rules to check input values..

for (var i = 0; i < uniqueArray.length; i++) {
    var input = uniqueArray[i];
    if(input.classList.contains("email") && input.value != ""){                    
        var mailPattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
        if(input.value.match(mailPattern)){
            // Remove Error Is any shown 
        }else{
            // Remove Error Is any shown so its not duplicated
            isValidationStatus = false;            
            // hear Add Error Eliment to show after Field
        }  
    }else {
        removeErroHtml(input);
        if (input.value == "") {                         
            isValidationStatus = false;
            // hear Add Error Eliment to show after Field
        }
    }
}

In above code, we have added two types of validation: first one is Email Format Validation and second one is Simple Required input Element validation, so the form cannot be submitted without any data filled in it.

Now, time to show error after the field. So let's create a function for it..

For Show Error Element we will add Text after the input and some class. Also we can add one little enhancement by highlighting the input fields when they have an error.

function createErrorHtml(input, text) {
    input.classList.add("is-invalid");
    let container = input.parentElement;
    var node = document.createElement("div");
    var att = document.createAttribute("class");
    att.value = "invalid-feedback";
    node.setAttributeNode(att);
    var t = document.createTextNode(text);
    node.appendChild(t);
    container.appendChild(node);
}

Now let’s make function for Remove Error element..

function removeErroHtml(element) {
    element.classList.remove("error");
    let parentHtml = element.parentElement;
    element.classList.remove("is-invalid");
    let errDivElement = parentHtml.lastElementChild;
    if (errDivElement.classList.contains("invalid-feedback")) {
         errDivElement.remove();
    }
}

OK, we did it! Now, let’s put the entire code together…

import React, { useState} from 'react'
const DemoForm = (props) => {
  const [state, setState] = useState({
     SimpleInput: '',
     SimpleEmail:'',
 });
 const handleChange = e => {
    setState(prevState => {
        return {
            ...prevState,
            [e.target.name]: e.target.value
        }
    });
    CustomValidation(e, true)
 }
 const SubmitDemoFrm = ((e) => {
   e.preventDefault();
    if(CustomValidation(e)){
      console.log('All Fields are valid. Form will be submit');
   }else{
     alert('some fields are invalid, message will appear after input.');
    }
 });
 const CustomValidation = (e, customeField = false) => {
   var isValidationStatus = true;
   var allFormElement = [];
   if (customeField) {
      allFormElement.push(e.target);
   } else {
     for (let index = 0; index < e.target.length; index++) {
       allFormElement.push(e.target[index]);
     }
    }
    var inputs = Array.prototype.filter.call(
      allFormElement,
      function (element) {
        if (element.classList.contains("required")) {
        return element.nodeName === "INPUT" || element.nodeName === "TEXTAREA";
        }
      }
    );
     var uniqueArray = [
       ...new Map(inputs.map((item) => [item["name"], item])).values(),
     ];
     for (var i = 0; i < uniqueArray.length; i++) {
       var input = uniqueArray[i];
         if(input.classList.contains("email") && input.value != ""){                    
           var mailPattern = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
             if(input.value.match(mailPattern)){
                removeErroHtml(input);
             }else{
              removeErroHtml(input);
              isValidationStatus = false;
              createErrorHtml(input,"Please Enter Valid Mail Address.");
              }  
      }else {
      removeErroHtml(input);
      if (input.value == "") {                         
        isValidationStatus = false;
        createErrorHtml(
        input,
        "This field is required."
       );
      }
     }
   }
        
   function createErrorHtml(input, text) {
     input.classList.add("is-invalid");
     let container = input.parentElement;
     var node = document.createElement("div");
     var att = document.createAttribute("class");
     att.value = "invalid-feedback";
     node.setAttributeNode(att);
     var t = document.createTextNode(text);
     node.appendChild(t);
     container.appendChild(node);
    }
    function removeErroHtml(element) {
     element.classList.remove("error");
     let parentHtml = element.parentElement;
     element.classList.remove("is-invalid");
     let errDivElement = parentHtml.lastElementChild;
     if (errDivElement.classList.contains("invalid-feedback")) {
        errDivElement.remove();
      }
    }
     return isValidationStatus;
    }
 return (<>
   <div className="page-content">               
     <section className="content">
       <form onSubmit={SubmitDemoFrm} id="myform" >
         <div className="card card-secondary">
           <div className="card-header">
             <h3 className="card-title"> Demo Form</h3>
           </div>
           <div className="card-body">
             <div className="row">
               <div className="col-3">
                 <div className="form-group">
                   <label> Simple Required Input <span>*</span></label>
                   <input onChange={handleChange} type="text" className="form-control required" name="SimpleInput" value={state.SimpleInput} />
                 </div>
               </div>
             </div>
             <div className="col-3">
               <div className="form-group">
                 <label> Required Email <span>*</span></label>
                 <input onChange={handleChange} type="text" className="form-control required email" name="SimpleEmail" value={state.SimpleEmail} />
               </div>
             </div>
           </div>
           <div className="card-footer">
             <input type="submit" className="btn btn-primary" value="Add" />
           </div>
         </div>
       </form>
     </section>
   </div>
 </>
 )
}
export default DemoForm;

Here we have validated only two conditions, you can add more as per your requirement, just need to add logic once and you can use it in every form by just adding Classname.