# (Part 2) Build quality forms with React 🚀

*Photo by  [Kelly Sikkema](https://unsplash.com/@kellysikkema)*

This article is the second and final part of "Build quality forms with React 🚀". [In the first part](https://javascript-for-breakfast.com/build-quality-forms-with-react), I explained what controlled and uncontrolled forms are in React, and I introduced two wonderful libraries: [react-hook-form](https://react-hook-form.com/) and [yup](https://github.com/jquense/yup). 

## Putting it all together

To demonstrate how it all ties up together, I will walk you through the process of building a simple *yet solid* login form. It will be made of two inputs (email and password), a checkbox "remember me" to keep the user session alive, and a submit button. We will also add a link at the bottom of the form, to switch to the registration form that I will also show you later. 

We won't be implementing any real authentication, the goal here is to focus only on the form and its validation. However, I will use Bootstrap class utilities for styling, so you get a good idea of what a "real-life" form would look like code-wise. Here is what the form will look like:


![Login Form](https://cdn.hashnode.com/res/hashnode/image/upload/v1623845692016/ypmEoCeocS.png)

And here is what happens if we hit the login button without filling out the email nor the password:

![Login Form Validation](https://cdn.hashnode.com/res/hashnode/image/upload/v1623845714050/z9gF9wx1r.png)

First, we start by declaring the Yup schema:

```jsx
const schema = yup.object().shape({
  email: yup
    .string()
    .email('This is not a valid email address')
    .required('Please fill in your email'),
  password: yup.string().required('Please fill in your password'),
  rememberMe: yup.boolean(),
})
```

Pretty simple, as you can see. It works by chaining validators for a specific field. For example, an email should be:

1. a string: `.string()`
2. also it should look like an email address: `.email()`
3. and it is required: `.required()`

You can specify an error message for each validation type, which is what I did, in order to provide accurate messages depending on the failure reason. 

Once this is done, the next step is to **type** our form values:

```tsx
// Type definition for the form data
type FormValues = {
  email: string
  password: string
  rememberMe: boolean
}
```

We will provide this type to react-hook-form, which will provide us a type-safe developer experience later when registering our inputs. 

Speaking of react-hook-form, it's now time to start writing our login form component. We start by initializing the form using the `useForm` hook:

```tsx
const {
  register, 
  handleSubmit, 
  formState: { errors },
} = useForm<FormValues>({ resolver: yupResolver(schema) })
```

As you can see, we destructure the return of `useForm` to get what we need. In our case, we only need a few things:

- `register` to register our inputs later
- `handleSubmit` to wrap our submit handler (so that react-hook-form can trigger form validation before calling it)
- `formState`: we can get many state values out of this, but here we only are interested in our form errors. If a validation error occurs for a specific field when submitting, the error message will be stored in `errors.email.message` for example

We type `useForm` with our `FormValues` to tell react-hook-form about the structure of our form data, and finally, we specify one option, which is the resolver Yup. This is where it all comes together: react-hook-form will now use the yup validation schema we defined before to validate our form. For example, if the user types something that is not a valid email address in the email input and tries to submit the form, `errors.email.message` will be "Please fill in your email".

Let's write a simple submit handler which will just log the form data to the console:

```js
const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data)
```

Thanks to TypeScript, `data` is strongly typed so we don't make mistakes while accessing and manipulating it:

![Form data typed - screenshot in vscode](https://cdn.hashnode.com/res/hashnode/image/upload/v1623845789285/w8DqPqtFp.png)

Now that everything is set up, let's render our form! As mentioned before, I'm using Bootstrap utility classes for styling purposes, just to demonstrate the fact that react-hook-form leaves the styling completely up to you. I won't explain what those classes do here, but you check [their documentation](https://getbootstrap.com/docs/4.3/utilities/) if you're curious. Anyway, here is the final version of our Login form:

```jsx
// LoginForm.tsx

import { SubmitHandler, useForm } from 'react-hook-form'
import { Link } from 'react-router-dom'
import * as yup from 'yup'
import { yupResolver } from '@hookform/resolvers/yup'
import cn from 'classnames'

// Yup validation schema
const schema = yup.object().shape({
  email: yup
    .string()
    .email('This is not a valid email address')
    .required('Please fill in your email'),
  password: yup.string().required('Please fill in your password'),
  rememberMe: yup.boolean(),
})

// Type definition for the form data
type FormValues = {
  email: string
  password: string
  rememberMe: boolean
}

/**
 * Simple Login Form Component
 */
export default function LoginForm() {
  // Initiate the form using react-hook-form's hook
  // We get some utilities & state in return
  const {
    register, // you need this to register your inputs
    handleSubmit, // this is a wrapper for your onSubmit handler
    formState: { errors }, // we get our form's state, especially the errors
  } = useForm<FormValues>({ resolver: yupResolver(schema) })

  const onSubmit: SubmitHandler<FormValues> = (data) => console.log(data)

  return (
    /**
     * A few things to note here:
     *  - the onSubmit callback needs to be wrapped with react-hook-form's
     *    handleSubmit utility
     *  - the "noValidate" prop on the form is here because we manage
     *    validation ourselves, with Yup, so we don't want the browser validation
     **/
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      {/** Email */}
      <div className="mb-3">
        <label htmlFor="email" className="form-label">
          Email address
        </label>
        <input
          id="email"
          type="email"
          className={cn('form-control', {
            /**
             * is-invalid is a Bootstrap utility class to highligh the
             * input in red in case of an error. If errors.email is not
             * nullish, it means there were a validation error regarding
             * this field
             */
            'is-invalid': Boolean(errors.email),
          })}
          {...register('email')}
        />
        {/** Render the error message related to this field (empty if no error) */}
        <div className="invalid-feedback">{errors.email?.message}</div>
      </div>

      {/** Password */}
      <div className="mb-3">
        <label htmlFor="password" className="form-label">
          Password
        </label>
        <input
          id="password"
          type="password"
          className={cn('form-control', {
            'is-invalid': Boolean(errors.password),
          })}
          {...register('password')}
        />
        <div className="invalid-feedback">{errors.password?.message}</div>
      </div>

      {/** Remember me */}
      <div className="mb-3 form-check">
        <input
          id="rememberMe"
          type="checkbox"
          className="form-check-input"
          {...register('rememberMe')}
        />
        <label className="form-check-label" htmlFor="rememberMe">
          Remember me
        </label>
      </div>

      {/** Submit button */}
      <button type="submit" className="btn btn-primary w-100 mb-3">
        Login
      </button>

      <Link to="/register">You're new ? Register here !</Link>
    </form>
  )
}
```

Here we go, pretty powerful, right? The entire validation process and the state management are being handled by react-hook-form and yup so we can focus on making a beautiful and accessible form. 

Earlier, I promised you a registration form, and I'm a man of my word, so here you go. Also, this allows me to demonstrate a few more things:

- A few more yup validation rules, like `oneOf` which is used to easily validate that the "Confirm password" value is the same as "Password", or to make a boolean required (without this, the value "false" would pass the required rule, because it's not nullish)
- The integration of a more complex UI component with `react-select`: this is done using react-hook-form's Controller, which is amazingly simple. You provide it a `control` prop (retrieved from the hook) and a `render` prop to render your custom component using the same benefits as with `register`
- The use of UI components instead of HTML markup, just to once again demonstrate that react-hook-form leaves the styling completely up to you

The form will look like this:

![Registration form](https://cdn.hashnode.com/res/hashnode/image/upload/v1623845915721/Nced60N2P.png)

![Untitled 4.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1623845932017/CNNFEiCFV.png)

Here is the code:

```jsx
// RegisterForm.tsx

import { useForm, Controller } from 'react-hook-form'
import { Link } from 'react-router-dom'
import Select, { OptionTypeBase } from 'react-select'
import * as yup from 'yup'
import { yupResolver } from '@hookform/resolvers/yup'

// For this one, we will use react-boostrap component instead of HTML markup
// for the form (because why not ? it's up to us 👌)
import Form from 'react-bootstrap/Form'
import Button from 'react-bootstrap/Button'

// Options for react-select
// We use this to ask the user about is favorite ice cream 🍦
const iceCreamOptions: OptionTypeBase[] = [
  { value: 'chocolate', label: 'Chocolate' },
  { value: 'strawberry', label: 'Strawberry' },
  { value: 'vanilla', label: 'Vanilla' },
]

// Yup validation schema
const schema = yup.object().shape({
  email: yup
    .string()
    .email('This is not a valid email address')
    .required('Please fill in your email'),
  password: yup.string().required('Please fill in your password'),
  passwordConfirm: yup
    .string()
		// Simple trick to validate that passwordConfirm is the same as passwor
    .oneOf([yup.ref('password')])
    .required('Passwords must match'),
  // favoriteIceCream isn't required, so we require its value to be one of the options, or null
  favoriteIceCream: yup.string().oneOf([...iceCreamOptions.map((option) => option.value), null]),
  acceptLegal: yup.boolean().oneOf([true], 'You must accept the Terms & Conditions'),
})

// Type definition for the form data
type FormValues = {
  email: string
  password: string
  passwordConfirm: string
  favoriteIceCream: OptionTypeBase
  acceptLegal: boolean
}

/**
 * Registration form
 */
export default function RegisterForm() {
  const {
    register,
    handleSubmit,
    formState: { errors },
    control,
  } = useForm<FormValues>({ resolver: yupResolver(schema) })

  function onSubmit(data: FormValues) {
		// When this is being executed, you know for sure that the form data is valid
		// according to the yup validation schema defined above
    const { passwordConfirm, acceptLegal, ...rest } = data
    console.log(rest) // you would only send what's in rest to your backend
  }

  return (
    <Form onSubmit={handleSubmit(onSubmit)} noValidate>
      {/** Email */}
      <Form.Group controlId="email" className="mb-3">
        <Form.Label>Email address</Form.Label>
        <Form.Control type="email" isInvalid={Boolean(errors.email)} {...register('email')} />
        <Form.Text className="invalid-feedback">{errors.email?.message}</Form.Text>
      </Form.Group>
      {/** Password */}
      <Form.Group controlId="password" className="mb-3">
        <Form.Label>Password</Form.Label>
        <Form.Control
          type="password"
          isInvalid={Boolean(errors.password)}
          {...register('password')}
        />
        <Form.Text className="invalid-feedback">{errors.password?.message}</Form.Text>
      </Form.Group>
      {/** Password Confirmation */}
      <Form.Group controlId="passwordConfirm" className="mb-3">
        <Form.Label>Confirm password</Form.Label>
        <Form.Control
          type="password"
          isInvalid={Boolean(errors.passwordConfirm)}
          {...register('passwordConfirm')}
        />
        <Form.Text className="invalid-feedback">{errors.passwordConfirm?.message}</Form.Text>
      </Form.Group>
      {/* Favorite Ice Cream */}
      <Form.Group controlId="favoriteIceCream" className="mb-3">
        <Form.Label>Favorite ice cream</Form.Label>
        <Controller
          name="favoriteIceCream" // equivalent to register('favoriteIceCream')
          control={control}
          render={({ field }) => <Select {...field} options={iceCreamOptions} />}
        />
      </Form.Group>
      {/** Accept terms & conditions */}
      <Form.Group controlId="acceptLegal" className="mb-3">
        <Form.Check
          type="checkbox"
          isInvalid={Boolean(errors.acceptLegal)}
          feedback={errors.acceptLegal?.message}
          label={
            <>
              I accept the <a href="#">Terms & Conditions</a>
            </>
          }
          {...register('acceptLegal')}
        />
      </Form.Group>

      {/** Submit button */}
      <Button variant="primary" type="submit" className="w-100 mb-3">
        Register
      </Button>
      <Link to="/">Already have an account ? Login</Link>
    </Form>
  )
}
```

## Conclusion

I hope that you enjoyed this humble guide to building quality forms with React, using two wonderful libraries: [react-hook-form](https://react-hook-form.com/) and [yup](https://github.com/jquense/yup). This is what I use every time I need to build a form, and I feel really happy and confident about the process. The fact that I can focus on the UI, features, and accessibility without worrying too much about state management or validation myself is absolutely priceless, and it saves me a lot of time. 

[Check out the repository with all 4 forms](https://github.com/Alarid/react-awesome-forms) (Controlled, Uncontrolled, Login and Register) with a fully functional user navigation using react-router, or [open it in a code sandbox](https://githubbox.com/Alarid/react-awesome-forms) (read my post on [CodeSandbox & Github Magic](https://javascript-for-breakfast.com/codesandbox-and-github-magic) to learn more about this kind of magic links 🧙)

Feel free to let me know what your thoughts are on the subject in the comments, and share this post with your peers if you liked it!
