Form validation with Next.js/React part 2

Learn how to validate custom input components with "react hook form"

TODO: provide alt

Hello friends,

Video tutorial for this blog post you can find here: https://youtu.be/-scXzb-F_3k

Starting point for this tutorial: https://github.com/Jerga99/next-youtube-course/tree/v1.2

Complete code: https://github.com/Jerga99/next-youtube-course/commit/e31b92302b2878124dee09db32ec26d7535e611a

In this part of form validation we are going to validate date component implemented by https://www.npmjs.com/package/react-datepicker .First we are going to provide startDate and endDate fields to the form, then we will fill them up with actual dates from react-datepicker. Let's start!

I hope your project was successfully setup. You can navigate into components/shared/PortfolioForm.js file.

Let's install datepicker: npm install --save react-datepicker

After installation, we can import styles into _app.js file.

import'react-datepicker/dist/react-datepicker.css';

Now back to PortfolioForm, let's import and provide a DatePicker component.

import { useEffect } from 'react';
import { useForm } from "react-hook-form";
import { Alert } from 'react-bootstrap';
import DatePicker from "react-datepicker";

const PortfolioForm = () => {
  const { handleSubmit } = useForm();
  
  const onSubmit = data => {
    alert(JSON.stringify(data));
  }
  
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="form-group">
        <label htmlFor="startDate">Start Date</label>
        <div>
          <DatePicker
            showYearDropdown
            selected={null}
            onChange={date => {alert(date)}}
          />
        </div>
      </div>

      <div className="form-group">
        <label htmlFor="endDate">End Date</label>
        <div>
        <DatePicker
            showYearDropdown
            selected={null}
            onChange={date => {alert(date)}}
          />
        </div>
      </div>
    </form>
  )
}

export default PortfolioForm;
PortfolioForm.js

To a DatePicker component we need to provide a function to retreive the date as onChange prop and also selected prop to let know the component which date is currently selected.

Currently onChange is just a function to alert the date and selected is set to null so we cannot really change the date.

Lets change this.

First we need to register startDate and endDate to react hook form. We can do it like this:

const { register, handleSubmit, watch } = useForm();

const startDate = watch('startDate');
const endDate = watch('endDate');

useEffect(() => {
  register({name: 'startDate', type: 'custom'});
  register({name: 'endDate', type: 'custom'});
}, [])

We will register startDate and endDate with register function in useEffect, but even before that we are going to set watchers to startDate and endDate.

Every time value of startDate and endDate will change component will re-render.

No we can provide values to the component.

<DatePicker
  showYearDropdown
  selected={startDate}
  onChange={date => {alert(date)}}
/>


<DatePicker
  showYearDropdown
  selected={endDate}
  onChange={date => {alert(date)}}
/>

This is nice but we still cannot display newly selected dates in our inputs. In onChange function we need explicitly set date to react hook form. We can do so with setValue function.

const { register, handleSubmit, watch, setValue } = useForm();

const handleDateChange = dateType => date => {
  setValue(dateType, date)
};


<DatePicker
  showYearDropdown
  selected={startDate}
  onChange={handleDateChange('startDate')}
/>


<DatePicker
  showYearDropdown
  selected={endDate}
  onChange={handleDateChange('endDate')}
/>

handleDateChange function will execute setValue function with 2 values, dateType which can be either “startDate” or “endDate”, and date value itself.

Now we can successfully get a date from the component when we are submitting the form!

Let's talk about validation now.

First let's create custom validator which validate if selected date is in the present or in the past. We don't want to select future days.

For example if today is 17. January 2020 then only 17.January and every day bellow is allowed.

Here is the validator function:

const isDateInFuture = date => {
  if (!date) { return false; }
  const today = new Date();
  today.setHours(0,0,0,0);
 
  return !(date > today);
}

If we don’t have a date or date is in the future then the date is not valid. To indicate this we are returning false value.

Now we need to provide the validator to register in useEffect.

useEffect(() => {
  register({name: 'startDate', type: 'custom'}, {validate: { isDateInFuture }});
  register({name: 'endDate', type: 'custom'}, {validate: { isDateInFuture }});
}, [])

Now when form will be submitted we can retrieve and display errors.

<DatePicker
  showYearDropdown
  selected={startDate}
  onChange={handleDateChange('startDate')}
/>
  { errors.startDate &&
  <Alert variant="danger">
    { errors.startDate?.type === "isDateInFuture" && <p>Please choose present or past date!</p> }
  </Alert>
  }
</DatePicker>



<DatePicker
  showYearDropdown
  selected={endDate}
  onChange={handleDateChange('endDate')}
/>
  { errors.endDate &&
    <Alert variant="danger">
      { errors.endDate?.type === "isDateInFuture" && <p>Please choose present or past date!</p> }
    </Alert>
  }
</DatePicker>

That's not all. We want to also have validation active as we are changing the dates. Currently we are activating validation only when we are clicking submit button.

Let's modify handleDateChange

const { register, handleSubmit, errors, setError, clearError, watch, setValue } = useForm();

const handleDateChange = dateType => date => {
  if (!isDateInFuture(date)) {
    setError(dateType, 'isDateInFuture')
  } else {
    clearError(dateType, 'isDateInFuture');
  }
  setValue(dateType, date)
};

Every time we change the date, we are going to check if date is in the future. If selected date is in the future we will setError in any other case we will clearError.

All right guys! That should be it from validation of a custom component. Now you should be set to validate any custom component with react-hook-form.

For more courses visit: https://eincode.com

Cheers,

Filip