Search code examples
cssreactjsformsreact-bootstraprequired

Custom validation in react-bootstrap forms not working


I'm trying to add custom validation logic to my react-bootstrap forms, but isInvalid doesn't seem to be respected. As soon as validated={true} is set on the form, it always validates non-empty strings, regardless of isValid or isInvalid. The invalid message is displayed, but the field shows as green. See here:

enter image description here

Here is the offending code:

 <Form noValidate validated={formValidated} id="bidForm">
    <Form.Group className="mb-3" controlId="formBasicBidding">
       <Form.Label>Bid Amount</Form.Label>
       <InputGroup hasValidation className="mb-3">
           <InputGroup.Text id="inputGroupPrepend">$</InputGroup.Text>
           <Form.Control
              required
              value={bidAmount}
              isInvalid={!(parseInt(bidAmount) > 0) && formValidated}
              onChange={e => setBidAmount(e.target.value)}
           />
           <InputGroup.Text id="inputGroupAppend">.00</InputGroup.Text>
           <Form.Control.Feedback type="invalid">
               Please select a dollar amount {`>`} 0
           </Form.Control.Feedback>
        </InputGroup>
    </Form.Group>
    <...>
</Form>

I was able to get it to work using a pattern= regex on the input, but I'm planning to do more complex validation where a regex won't be sufficient. How can I get react-bootstrap to follow isValid and isInvalid properly? Thanks!


Solution

  • The docs don't do a great job of making this clear, but the form-level validated attribute hooks directly into HTML5's native validation library and can't be used simultaneously with other validation techniques. So if you're using the native option and an input doesn't violate any of the native validation options (required, pattern, etc), then the input will be considered valid. Since you don't have any of the native validation attributes besides required on your bid input, "foo" is valid as far as React-Bootstrap is concerned.

    (If you're thinking 'well that's bad code design to let you use both simultaneously', I'd tend to agree with you.)

    You'll note that with your current code, if you enter "foo" and submit the form the element actually does have an is-invalid class applied to it, but it's also got the :valid psuedo-selector applied to it because the entire form was validated, which seems to take precedence from a CSS perspective:

    enter image description here

    The best solution here is to use either the native HTML5 validation option (detailed here) or roll your own (with or without a helper library like Formik), but not both simultaneously.

    If you're trying to avoid validating until the input is actually in a "dirty" state (e.g. the user filled it out or submitted the form), you can do something like this (CodePen here):

            <Form.Control
              required
              value={bidAmount}
              isInvalid={!(parseInt(bidAmount) > 0) && (bidAmount.length || formSubmitted)}
              onChange={(e) => setBidAmount(e.target.value)}
            />