Controlled Forms with Frontend Validations using React-Bootstrap
Tags: react, bootstrap, forms, validations
Published: 2021-01-29 22:05:54

I've been working on my capstone project for the last couple of weeks, and with it I've had a chance to learn a lot more about react-bootstrap for putting together functional, and aesthetically pleasing web pages.  One place that this framework has really helped me to up my game is in creating responsive forms.  Pairing with React hooks, you can very easily make forms that store input in state, keep control form values, and display invalidations when necessary.  Lets create a simple form with react & react-bootstrap to see how it's done!

[Link to Repo](https://github.com/AlecGrey/demo-form-for-blog)

### App Setup

We're going to build a simple form with a few fields.  To start, lets initialize our app with `npx create-react-app form-demo`.  Next we're going to add react-bootstrap to our project with either `npm install --save react-bootstrap` or `yarn add react-bootstrap`.  

Because React-Bootstrap comes with specific out-of-the-box styling, it is also helpful to add vanilla-bootstrap for additional customization.  To do this, start with either `npm install --save bootstrap`, or `yarn add bootstrap`, then import it into your index.js or App.js files:
```javascript
// ./src/App.js
// ...other imports
import 'bootstrap/dist/css/bootstrap.min.css';
``` 
Now that our app is set up, we can start building out our basic form.

### Form Building with React-Bootstrap

Like all components, we need to use `import` in order to bring them in for availability in our app.  Now that we have the library installed, we can easily add react-bootstrap components to our app:
```javascript
// ./src/App.js
// ...other imports
import Form from 'react-bootstrap/Form';
```

This convention is consistent throughout the library, but I highly suggest reviewing the [documentation](https://react-bootstrap.github.io/components/alerts/) for specific import instructions.

Building the form follows very straightforward convention, but also keeps room open for styling choices to be mixed in.  Here is the code for our form, which will be used to review food items at a restaurant:

```javascript
const App = () => {
  return (
    <div className='App d-flex flex-column align-items-center'>
      <h1>How was your dinner?</h1>
      <Form style={{ width: '300px' }}>
        <Form.Group>
          <Form.Label>Name</Form.Label>
          <Form.Control type='text'/>
        </Form.Group>
        <Form.Group>
          <Form.Label>Food?</Form.Label>
          <Form.Control as='select'>
            <option value=''>Select a food:</option>
            <option value='chicken parm'>Chicken Parm</option>
            <option value='BLT'>BLT</option>
            <option value='steak'>Steak</option>
            <option value='salad'>Salad</option>
          </Form.Control>
        </Form.Group>
        <Form.Group>
          <Form.Label>Rating</Form.Label>
          <Form.Control type='number'/>
        </Form.Group>
        <Form.Group>
          <Form.Label>Comments</Form.Label>
          <Form.Control as='textarea'/>
        </Form.Group>
        <Button type='submit'>Submit Review</Button>
      </Form>
    </div>
  )
}
```
#### Lets break this down:

* Following React convention, we have the div wrapping the rest of the component.
* We wrap the entire form in a single `Form` component
* Each field is *grouped* using the `Form.Group` component wrapper.  This generally follows a 1:1 rule for Group:Field, but there are advanced cases such as having multiple fields on a single row where you could wrap multiple fields.
* Use `Form.Label` for labelling each field.  You can use added styling on the form group in order to make this display inline with your form input, but by default they will stack vertically.
* Use `Form.Control` to designate the input field.  Here we have a couple of options for inputs.  If your field resembles an HTML input tag, you can use `type='type'` to determine what type of input field it will be.  In our example we use `type='text'` and `type='number'`.  If you will be using another HTML tag, such as a `<select>` tag, you can use the `as='tag'` designation to determine what you get.  In our example we use both an `as='select'` and an `as='textarea'` to designate these.
* To submit the form, we add a button to the bottom with a `type='submit'` designation.  Personally, I prefer not to use the 'submit' type, as we will more than likely be overriding the default submit procedure anyway.

As you can see, we can very quickly build a form that is aesthetically pleasing, but the important next step is to make it functional!

### Updating State with Form Input

Using react hooks, we're going to create 2 pieces of state: the `form` and the `errors`.

```javascript
const [ form, setForm ] = useState({})
const [ errors, setErrors ] = useState({})
```

The `form` object will hold a key-value pair for each of our form fields, and the `errors` object will hold a key-value pair for each error that we come across on form submission.

To update the state of `form`, we can write a simple function:

```javascript
const setField = (field, value) => {
    setForm({
      ...form,
      [field]: value
    })
  }
```

This will update our state to keep all the current form values, then add the newest form value to the right key location.

We can now add callback functions for `onChange` on each form field:

```javascript
// do for each Form.Control:
<Form.Label>Name</Form.Label>
<Form.Control type='text' onChange={ e => setField('name', e.target.value) }/>
```

As you can see, we are setting the key of 'name' to the value of the input field.  If your form will be used to create a new instance in the backend, it is a good idea to set the key to the name of the field that it represents in the database.

Great!  Now we have a form that updates a state object when you change the value.  Now what about when we submit the form?

### Checking for errors on submit

We now need to check our form for errors!  Think about what we **don't** want our backend to receive as data, and come up with your cases.  In our form, we don't want

- Blank or null values
- Name must be less than 30 characters
- Ratings above 5 or less than 1
- Comments greater than 100 characters

Using these cases, we're going to create a function that checks for them, then constructs an `errors` object with error messages:

```javascript
const findFormErrors = () => {
    const { name, food, rating, comment } = form
    const newErrors = {}
    // name errors
    if ( !name || name === '' ) newErrors.name = 'cannot be blank!'
    else if ( name.length > 30 ) newErrors.name = 'name is too long!'
    // food errors
    if ( !food || food === '' ) newErrors.food = 'select a food!'
    // rating errors
    if ( !rating || rating > 5 || rating < 1 ) newErrors.rating = 'must assign a rating between 1 and 5!'
    // comment errors
    if ( !comment || comment === '' ) newErrors.comment = 'cannot be blank!'
    else if ( comment.length > 100 ) newErrors.comment = 'comment is too long!'

    return newErrors
}
```

Perfect.  Now when we call this, we will be returned an object with all the errors in our form.

#### Let's handle submit now, and check for errors.  Here is our order of operations:

1. Prevent default action for a form using `e.preventDefault()`
2. Check our form for errors, using our new function
3. If we receive errors, update our state accordingly, otherwise proceed with form submission!

now to handle submission:

```javascript
const handleSubmit = e => {
    e.preventDefault()
    // get our new errors
    const newErrors = findFormErrors()
    // Conditional logic:
    if ( Object.keys(newErrors).length > 0 ) {
      // We got errors!
      setErrors(newErrors)
    } else {
      // No errors! Put any logic here for the form submission!
      alert('Thank you for your feedback!')
    }
  }
```

By using `Object.keys(newErrors).length > 0` we are simply checking to see if our object has any key-value pairs, or in other words, did we add any errors.

Now that we have errors, we need to display them in our form!  This is where we will add our last bit of React-Bootstrap spice: `Form.Control.Feedback`.

### Setting Invalidations and Feedback

React bootstrap allows us to add a feedback field, and to tell it *what* and *when* to display information.

On each of our forms, we will add an `isInvalid` boolean, and a React-Bootstrap Feedback component tied to it:

```javascript
<Form.Group>
    <Form.Label>Name</Form.Label>
    <Form.Control 
        type='text' 
        onChange={ e => setField('name', e.target.value) }
        isInvalid={ !!errors.name }
    />
    <Form.Control.Feedback type='invalid'>
        { errors.name }
    </Form.Control.Feedback>
</Form.Group>
```

With this added, Bootstrap will highlight the input box red upon a true value for `isInvalid`, and will display the error in `Form.Control.Feedback`.

There's one final step however!  We need to *reset* our error fields once we have addressed the errors.  My solution for this is to update the errors object in tandem with form input, like so:

```javascript
const setField = (field, value) => {
    setForm({
      ...form,
      [field]: value
    })
    // Check and see if errors exist, and remove them from the error object:
    if ( !!errors[field] ) setErrors({
      ...errors,
      [field]: null
    })
  }
```

Now, when a new input is added to the form, we will reset the errors at that place as well.  Then on the next form submission, We can check for errors again!

### Final product in action:

![Demonstration gif](https://dev-to-uploads.s3.amazonaws.com/i/aw9p9jfwhrdaqjeqwmqv.gif)

Thanks for reading! I hope this was helpful.