title: Bootstrap 4 form validation not working while Using invalid-feedback even when input 'is-valid'
body: <p>I'm trying to do very basic form validation with Bootstrap 4. For some reason, when adding class 'is-valid' to my 2nd input in my example, because the 1st input has the 'is-invalid' class, the 2nd input will have green borders (as it should since it's valid!), <strong>BUT</strong> it'll also have the invalid-feedback div showing (the &quot;this field is required&quot; message!).</p>
  <p>See:</p>
  <p><a href="https://i.stack.imgur.com/B38LW.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/B38LW.png" alt="enter image description here" /></a></p>
  <p>I'm not sure what I'm doing wrong here... Here's the code:</p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>/**
   * AJAX Post script
   */
  
  const ERROR_TYPE_FATALERROR = 1;
  const ERROR_TYPE_INPUTERROR = 2;
  const ERROR_TYPE_GLOBALMESSAGE = 3;
  
  
  // Run through the validate() function ONLY once the form as been submitted once!
  // Otherwise, user will get validation right away as he types! Just a visual thing ...
  var formSubmittedOnce = false;
  
  /**
   * submitFormData()
   * Serialize and post form data with an AJAX call
   *
   * Example: onClick="submitFormData('frm1',['username','email'])"
   * 
   * @param string formid essentially the 'id' of the container holding all form elements (i.e. &lt;tr id="rowfrm_1"&gt;, &lt;form id='frm1'&gt;, etc.)
   * @param array fields list of field names that may produce input errors (i.e. ['username','email'] )
   */
   function submitFormData(formid, fields) {
    
      // flag form was submitted once!
      formSubmittedOnce = true;
    
      // ----------------------------------
      // first rehide all error containers
      // ----------------------------------    
      $('#fatalError').removeClass('d-block');
      $('#fatalErrorID').removeClass('d-block');
      $('#fatalErrorTrace').removeClass('d-block');
      $('#fatalErrorGoBack').removeClass('d-block');
      
      $('#globalMessage').removeClass('d-block');
      $('#globalMessageID').removeClass('d-block');
      $('#globalMessageTrace').removeClass('d-block');
      $('#globalMessageFooter').removeClass('d-block');
      $('#globalMessageMailLink').removeClass('d-block');
      $('#globalMessageGoBackLink').removeClass('d-block');
  
      // rehide error containers of all inputs that might produce errors 
      if (fields != null){ 
        for (const f of fields) {
          $('#' + f + '_inputError').removeClass('d-block');
        }
      }
  
  
      // ----------------------------------
      // loop form elements and validate required fields
      // ----------------------------------
      var formNode = $("#"+formid);
      var formInputs = formNode.find("select, textarea, input");    
      var submit = true;
  
      for(var i = 0; i &lt; formInputs.length; ++i) {
        var input = formInputs[i];
  
        // validate fields
        if( validate(input) === false ){
          submit = false;
        }
      } 
  
  
      if(submit === true) {
  
      // ----------------------------------
      // get form data and serialize it!
      // ----------------------------------
    
        // formid comes from a &lt;form&gt;. just serialize it
        if( formNode.prop("tagName") === "FORM" ){
          var formData = formNode.serialize();
        }
        // formid doesn't come from a &lt;form&gt;
        else {
    
          // get all form control
          var myInputs = formNode.clone();
    
          // bug with clone() and SELECT controls: it'll only get the value of the option having the 'selected' attribute (the default value)
          // this hack will change the value clone() got to the actual user selected value, and not the default set value!
          formNode.find('select').each(function(i) {
            myInputs.find('select').eq(i).val($(this).val());
          })
    
          // create a dummy form, append all inputs to it and serialize it.
          var formData = $('&lt;form&gt;').append(myInputs).serialize();
        }
  
  
        // ----------------------------------
        // POST !
        // ----------------------------------
        $.ajax({
          type: 'POST',
          url: $(location).attr('href'),
          data: formData,
          dataType : "json",
        }).done(function(response) {
    
          // get response
          if(response) {
      
            // if we got success, redirect if we got a redirect url!
            if ( response.success != null ) {
              if (typeof response.success === "string") {
                window.location.replace(response.success);
              }
            }
            
            // if anything else, PHP returned some errors or a message to display (i.e. 'data saved!')
            else { 
              showMessages(response);
            }
          }
    
          // Successful post, but no response came back !?
          // assume success, since no 'success' response came back, thus keeping same page as is
          else {
            console.warn("Post sent, but no response came back!? Assuming successful post...");
          }
    
        }).fail(function(xhr, status, error) { // we get here if we don't have a proper response/json sent!
          
          console.error("Ajax failed: " + xhr.statusText);
          console.error(status);
          console.error(error);
    
          var ajaxError = {
            'type' : ERROR_TYPE_FATALERROR,
            'message' : '&lt;strong&gt;Ajax failure!&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;' + status + '&lt;br/&gt;&lt;br/&gt;' + error, 
            'trace' : null, 
            'id' : null,
            'goback' : null,
            'adminMailtoLnk' : 'mailto:' + 'ravenlost2@gmail.com'
          };
    
          showMessages(ajaxError);
          
        });
      }
  }
  
  
  /**
   * showMessages()
   * show error messages in page based on JSON response
   * @param response JSON object holding response with (error) messages to display
   */
  function showMessages(response){
    
    // error type
    switch (response.type) {
      
      // ----------------------------
      // GLOBAL MESSAGE
      // ----------------------------
      case ERROR_TYPE_GLOBALMESSAGE:
        $('#globalMessage').addClass('d-block');
      
        // set global message header message type 
        $('#globalMessage').removeClass("error warning info");           
        $('#globalMessage').addClass(response.gmType);
  
        $('#globalMessageIcon').removeClass("fa-exclamation-triangle fa-info-circle");
        $('#globalMessageIcon').addClass(response.gmIcon);
                        
        $('#globalMessageTitle').empty();
        $('#globalMessageTitle').append(response.gmTitle);
          
        // set message
        $('#globalMessagePH').empty();
        $('#globalMessagePH').append(response.message);
          
        // set uniq error id
        if (response.id != null) {
          $('#globalMessageID').addClass('d-block');
          $('#globalMessageIDPH').empty();
          $('#globalMessageIDPH').append(response.id);
        }
    
        // set stacktrace
        if(response.trace != null) {
        $('#globalMessageTrace').addClass('d-block');
          $('#globalMessageTracePH').empty();
          $('#globalMessageTracePH').append(response.trace);
        }
          
        // set footer
        if( (response.showContactAdmin == true) || (response.goback != null) ) {
           
          $('#globalMessageFooter').addClass('d-block');
  
          // contact admin
          if(response.showContactAdmin == true) {
            $('#globalMessageMailLink').addClass('d-block');
            $('#globalMessageMailLinkPH').attr('href', response.adminMailtoLnk);
          }
  
          // go back 
          if(response.goback != null){
            $('#globalMessageGoBackLink').addClass('d-block');
            $('#globalMessageGoBackLinkPH').attr('href', response.goback);
          }
  
        }
  
        break;
  
      // ----------------------------
      // FATAL ERROR
      // ----------------------------              
      case ERROR_TYPE_FATALERROR:
  
        // hide content if we got a fatal as to prevent user from fiddling around and not reading the message!
        $('#content').addClass('d-none'); 
  
        $('#fatalError').addClass('d-block');
  
        // set message
        $('#fatalErrorMessagePH').empty();
        $('#fatalErrorMessagePH').append(response.message);
  
        // reset mailto link
        $('#fatalErrorMailLink').attr('href', response.adminMailtoLnk);
  
        // set stacktrace
        if (response.trace != null) {
        $('#fatalErrorTrace').addClass('d-block');
          $('#fatalErrorTracePH').empty();
          $('#fatalErrorTracePH').append(response.trace);
        }
  
        // set uniq error id
        if (response.id != null) {
          $('#fatalErrorID').addClass('d-block');
          $('#fatalErrorIDPH').empty();
          $('#fatalErrorIDPH').append(response.id);
        }              
  
        // set 'go back' url
        if(response.goback != null) {
          $('#fatalErrorGoBack').addClass('d-block');
          $('#fatalErrorGoBackLink').attr('href', response.goback);
        }
  
        break;
  
      // ----------------------------
      // INPUT ERROR
      // ---------------------------- 
      case ERROR_TYPE_INPUTERROR:
  
        for (var field in response.fields) {
          var msg = eval('response.fields.' + field);
          $('#' + field + '_inputError').addClass('d-block')
          $('#' + field + '_inputError_message').empty();
          $('#' + field + '_inputError_message').append(msg);
        }
  
        break;
  
      default:
        console.error('Got an invalid error type from the response!');
  
    }
  
  }
   
   /**
    * validate()
    * Validate if field is empty or not
    * @param input form element
    * @return boolean
    */
  function validate(input) {
  
    if(formSubmittedOnce === true) {
      
      if( input.hasAttribute('required') ) {
        if(input.value.trim() === '') {
          input.classList.remove('is-valid');
          input.classList.add('is-invalid');
          return false;
        }
        else {
          input.classList.remove('is-invalid');
          input.classList.add('is-valid');
          return true;
        }
      }
    
      else {
        // if we get here, then any other inputs not marked as 'required' are valid
        input.classList.add('is-valid');
      }
  
    }
    
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;html&gt;
  
  &lt;head&gt;
    &lt;link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" type="text/css"&gt;
    &lt;script src="https://code.jquery.com/jquery-3.5.1.min.js"&gt;&lt;/script&gt;
  &lt;/script&gt;
  &lt;/head&gt;
  
  &lt;body&gt;
  
  &lt;form id="testfrm" class="form-group"&gt;
  
  Username: &lt;input type="text" name="username" aria-describedby="username_required username_inputError" class="form-control is-invalid" oninput="validate(this)" required/&gt;&lt;br&gt;
  &lt;div id="username_required" class="pl-1 invalid-feedback"&gt;
    This field is required!
  &lt;/div&gt;
  
  &lt;!-- if bad username format or already taken, print form input error --&gt;
  &lt;div id="username_inputError" class="col alert alert-danger alert-dismissible fade show mt-2 py-2 pl-3 pr-5 text-left d-none"&gt;
    &lt;small&gt;
      &lt;strong&gt;Error!&lt;/strong&gt; &lt;span id="username_inputError_message"&gt;&lt;/span&gt;
      &lt;button type="button" aria-label="Close" class="close pt-1 pr-2" onclick="$('#username_inputError').removeClass('d-block').addClass('d-none');"&gt;×&lt;/button&gt;
    &lt;/small&gt;
  &lt;/div&gt;
  
  Email: &lt;input type="text" name="email" aria-describedby="email_required email_inputError" class="form-control is-valid" oninput="validate(this)" required/&gt;&lt;br&gt;
  &lt;div id="email_required" class="pl-1 invalid-feedback"&gt;
    This field is required!
  &lt;/div&gt;
  
  &lt;!-- if bad email format or already taken, print form input error --&gt;
  &lt;div id="email_inputError" class="col alert alert-danger alert-dismissible fade show mt-2 py-2 pl-3 pr-5 text-left d-none"&gt;
    &lt;small&gt;
      &lt;strong&gt;Error!&lt;/strong&gt; &lt;span id="email_inputError_message"&gt;&lt;/span&gt;
      &lt;button type="button" aria-label="Close" class="close pt-1 pr-2" onclick="$('#email_inputError').removeClass('d-block').addClass('d-none');"&gt;×&lt;/button&gt;
    &lt;/small&gt;
  &lt;/div&gt;
  
  Comment: &lt;input type="text" name="comment" class="form-control is-valid"&gt;&lt;br&gt;
  
  &lt;input type="button" value="Submit" onclick="submitFormData('testfrm',['username','email'])" class="is-valid"&gt;
  &lt;/form&gt;
  
  &lt;script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js" integrity="sha384-6khuMg9gaYr5AxOqhkVIODVIvm9ynTT5J4V1cfthmT+emCG6yVmEZsRHdxlotUnm" crossorigin="anonymous"&gt;&lt;/script&gt;
  
  
  
  &lt;/body&gt;
  &lt;/html&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>If anyone can shed a light on this.. Cheers! Pat</p>