ReactJS – How to validate a form?

Use react-hook-form

Basic

Install

yarn add react-hook-form

Usage

  • Create form elements with unique name props
...

return (
  <form>
    <input name="name" />
    <input type="email" name="email" />
    <input type="password" name="password" />
    <button>Submit</button>
  </form>

...
  • Add handleSubmit method to manage form submission
import { useForm } from "react-hook-form";

...

  const { register, handleSubmit } = useForm();

  const onFormSubmit  = data => console.log(data);
  const onErrors = errors => console.error(errors);

  return (
    <form onSubmit={handleSubmit(onFormSubmit, onErrors)}>
      ...
    </form>
  );
  • Register input fields into React Hook Form by using name values
import { useForm } from "react-hook-form";

...

  const { register, handleSubmit } = useForm();

...

  return (
    <form onSubmit={handleSubmit(onFormSubmit, onErrors)}>
      <input name="name" {...register('name')} />
      <input type="email" name="email" {...register('email')} />
      <input type="password" name="password" {...register('password')} />
      <button>Submit</button>
    </form>
  );
  • Create form validation object
...

  const registerOptions = {
    name: { required: "Name is required" },
    email: { required: "Email is required" },
    password: {
      required: "Password is required",
      minLength: {
        value: 8,
        message: "Password must have at least 8 characters"
      }
    }
  };

...
  • Apply validation for form element
...

  <form onSubmit={handleSubmit(handleRegistration, handleError)}>
    <input name="name" type="text" {...register('name', registerOptions.name) }/>
    {errors?.name && errors.name.message}
    <input
      type="email"
      name="email"
      {...register('email', registerOptions.email)}
    />
    {errors?.email && errors.email.message}
    <input
      type="password"
      name="password"
      {...register('password', registerOptions.password)}
    />
    {errors?.password && errors.password.message}

    <button>Submit</button>
  </form>

...
  • Or catch error state like this
  const errorNameMessage = errors['name']?.message;
  const hasNameError = !!(errors && errorNameMessage);

  return  <form>
    <input name="name" type="text" {...register('name', registerOptions.name) }/>
    {hasNameError && errorNameMessage}
...

How to make a submit button inside a form?

<button type='submit'>Click me</button>

Typescript template

How to create main component?

import InputForm from 'src/components/InputForm'
import { useForm, SubmitHandler } from 'react-hook-form'

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

type TFormFields = {
  'name-first-name': string,
}

const registerRules = {
  firstName: { required: 'First name is required' },
  gender: { required: 'Gender is required' },
}

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

const Details = () => {
  const {
    control,
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<TFormFields>()

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <InputForm
        name='name-first-name'
        register={register}
        rules={registerRules.firstName}
        errors={errors}
      />
      <SelectBox
        placeHolder='Gender'
        name='name-select-gender'
        errors={errors}
        control={control}
        options={[
         { value: 'male', label: 'Male' },
         { value: 'female', label: 'Female' },
        ]}
        rules={registerRules.gender}
        />
    </form>
  )
}

export default Details

How to create a custom Input text?

import { ReactElement } from 'react'
import {
  DeepMap,
  FieldError,
  FieldValues,
  Path,
  RegisterOptions,
  UseFormRegister,
} from 'react-hook-form'
require('./index.scss')

type TInputProps<TSomething> = {
  placeHolder: string,
  name?: Path<TSomething>,
  rules?: RegisterOptions,
  register?: UseFormRegister<TSomething>,
  errors?: Partial<DeepMap<FieldValues, FieldError>>
}

const InputForm = <TSomething extends unknown>({
  name,
  rules = {},
  register,
  errors = {},
  placeHolder
}: TInputProps<TSomething>): ReactElement => {

  const errorMessage = errors[name]?.message
  const hasError = !!(errors && errorMessage)

  return (
    <div className={hasError ? `preferences__profile--input-box--error` : `preferences__profile--input-box`}>
      <input
        placeholder={placeHolder}
        name={name || ''}
        {...(register && register(name, rules))}
      />
      {hasError && <p>{errorMessage}</p>}
    </div>
  )
}

export default InputForm

How to create a custom Select box with react-select?

import { ReactElement } from 'react'
import Select, { components } from 'react-select'
import { Control, Controller, DeepMap, FieldError, FieldValues, Path, RegisterOptions } from 'react-hook-form'
require('./index.scss')

interface Props<TSomething> {
  placeHolder: string,
  options: Array<any>,
  name?: Path<TSomething>,
  rules?: RegisterOptions,
  control?: Control<TSomething, any>,
  errors?: Partial<DeepMap<FieldValues, FieldError>>,
}

let hasError: boolean = false

const SelectBox = <TSomething extends unknown>({
  placeHolder,
  options,
  rules,
  control,
  errors,
  name,
}: Props<TSomething>): ReactElement => {

  const errorMessage = errors?.[name]?.message
  hasError = !!(errors && errorMessage)

  return <>
    <Controller
      control={control}
      name={name}
      rules={rules}
      render={({ field: { onChange, value, name, ref } }) => {
        
        const currentSelection = options.find(
          (c) => c.value === value
        )

        const handleSelectChange = (selectedOption: { value: string }) => {
          onChange(selectedOption?.value);
        }

        return <div>
          <Select
            className={'preferences__select-box'}
            components={{
              DropdownIndicator: CustomDropdownIndicator,
              IndicatorSeparator: () => null,
            }}
            styles={customStyles}
            options={options}
            placeholder={placeHolder}
            onChange={handleSelectChange}
            value={currentSelection}
          />
          {hasError && <p className='preferences__profile--error-text'>{errorMessage}</p>}
        </div>
      }}
    />
  </>
}

const customStyles = {
  control: (provided: any) => ({
    ...provided,
    width: '100%',
    height: '48px',
    paddingLeft: '7px',
    border: hasError ? '1px solid #ED1C24' : '1px solid #DEDEDE',
    fontSize: '14px',
    color: '#333333',
  }),
  placeholder: (defaultStyles: any) => {
    return {
      ...defaultStyles,
      color: '#999999',
    }
  },
  option: (provided: any, state: { isSelected: any }) => ({
    ...provided,
    color: '#333333',
  }),
}

const CustomDropdownIndicator = (props: any) => {
  return (
    <components.DropdownIndicator {...props}>
      <YourOwnIcon />
    </components.DropdownIndicator>
  )
}

export default SelectBox

Be the first to comment

Leave a Reply

Your email address will not be published.


*