DEV Community

Cover image for Reusable form components using react + react hooks form + yup + typescript
kiranojhanp
kiranojhanp

Posted on

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>


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Installation



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


Enter fullscreen mode Exit fullscreen mode

Lets Build

Create 2 Components



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



Enter fullscreen mode Exit fullscreen mode

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;



Enter fullscreen mode Exit fullscreen mode

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;



Enter fullscreen mode Exit fullscreen mode

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;


Enter fullscreen mode Exit fullscreen mode

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

Top comments (1)

Collapse
 
kingsleykbc profile image
Anyabuike Kingsley

Great article, but a few notes:

  • Mapping the children would mean only inputs can be passed as children (with no blank space)
  • Passing handleSubmit and onClick is unnecessary.
  • Better to just have the useForm hook inside of the form hook, and pass the necessary props by making the form a higher order component. i.e. <> {(register, errors,...) => {...}} </>