Reusable form components using react + react hooks form + yup + typescript

Reusable form components using react + react hooks form + yup + typescript

Introduction

This Post helps to build a simple form with basic elements like input and select using react hooks form that manage form data, submission, and validation. By the end of this post, we will create reusable components with following syntax.

<Form>
 <Input name="email" type="email" />
 <Input name="password" type="password" />
</Form>

What is React hooks form?

A library to build Performant, flexible and extensible forms with easy-to-use validation. Check Official Website for more information.

What is Yup?

Yup is a straightforward JavaScript schema builder for value parsing and validation.

Motivation

I didn't find many resources online for reusable components for react hooks form, especially using typescript. I have written this blog post to share what I created in few hours. Feel free to comment improvements below.

Prerequisites

You can use this library in react and react based frameworks such as NextJS, GatsbyJS and even react native. I will be using a simple typescript project bootstrapped using create-react-app.

npx create-react-app my-app --template typescript

Installation

npm install --save react-hook-form @hookform/resolvers yup

Lets Build

Create 2 Components

├── src/
├── components
    ├── Form.tsx
    ├── Input.tsx
    ├── Usage.tsx

Form.tsx

We use this component as a simple form wrapper.

import React, { FC, createElement } from "react";
import { ReactNode } from "react";

export type classNameType = string;
export type childrenType = ReactNode;

export interface IFormProps {
  defaultValues?: any;
  children?: childrenType;
  buttonLabel?: string;
  onSubmit?: any;
  handleSubmit?: any;
  register?: any;
  className?: classNameType;
}

const Form: FC<IFormProps> = ({
  defaultValues,
  buttonLabel = "Submit",
  children,
  onSubmit,
  handleSubmit,
  register,
  ...rest
}) => {
  return (
    <form onSubmit={handleSubmit(onSubmit)} {...rest}>
      <div className="d-flex justify-content-center fields__email">
        {Array.isArray(children)
          ? children.map((child) => {
              return child.props.name
                ? createElement(child.type, {
                    ...{
                      ...child.props,
                      register,
                      key: child.props.name
                    }
                  })
                : child;
            })
          : children}
      </div>
      <button className="btn btn--brand">{buttonLabel}</button>
    </form>
  );
};

export default Form;

Input.tsx

We use this component for any input element (text,password,email,etc)

import React, { FC, InputHTMLAttributes } from "react";

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  name: string;
  label?: string;
  error?: string;
  register?: any;
  wrapperClass?: string;
  className?: string;
}

const Input: FC<InputProps> = ({
  register,
  name,
  error,
  label,
  wrapperClass,
  ...rest
}) => {
  return (
    <div className={wrapperClass}>
      {label && <label htmlFor={name}>{label}</label>}
      <input
        aria-invalid={error ? "true" : "false"}
        {...register(name)}
        {...rest}
      />
      {error && <span role="alert">{error}</span>}
    </div>
  );
};

export default Input;

Usage.tsx

Above components can be used in application as follows

import React from "react";
import Form from "./Form";
import Input from "./Input";
import * as yup from "yup";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";

// interface for form
interface EmailInterface {
  email: string;
  password: string;
}

// validation
const EmailSchema = yup.object().shape({
  email: yup
    .string()
    .email("Enter a valid email")
    .required("Email is required"),
  password: yup
    .string()
    .max(32, "Max password length is 32")
    .required("Password is required")
});

const Usage = () => {
  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm({ resolver: yupResolver(EmailSchema) });

  const onSubmit = (data: EmailInterface) => console.log(data);
  return (
    <Form
      buttonLabel="Change Email"
      register={register}
      handleSubmit={handleSubmit}
      onSubmit={onSubmit}
      className="change-form"
    >
      <Input
        name="email"
        type="email"
        placeholder="Enter your email"
        error={errors.email?.message}
        autoFocus
      />
      <Input
        name="password"
        type="password"
        placeholder="Password"
        error={errors.password?.message}
      />
    </Form>
  );
};

export default Usage;

Congrats! You have successfully created reusable input component using react hooks form, yup and typescript. Here's same project in codesandbox. Feel free to check.

COngratulation