Build Reusable and Efficient Forms with React Hook Form in TypeScript
Tags: react, reacthookform, nextjs, typescript
Published: 2024-01-05 18:30:03

**In the ever-evolving landscape of web development, creating forms that are not only user-friendly but also efficient and maintainable is crucial. React Hook Form has emerged as a powerful tool for managing forms in React and Next.js applications. In this article, we’ll explore how to harness the capabilities of React Hook Form to build reusable and high-performance forms in TypeScript, ensuring a seamless user experience while avoiding unnecessary re-renders.**

## Understanding the Power of React Hook Form

React Hook Form is a library designed to make form management in React applications intuitive and developer-friendly. Its key features include a simple API, minimal boilerplate, and efficient re-rendering strategies. Leveraging its hook-based approach, developers can create forms that are not only easy to set up but also performant.

**Creating a Reusable Form Component**

One of the best practices when working with React Hook Form is encapsulating form logic into reusable components. This not only promotes code reusability but also ensures a consistent structure across different parts of your application. Let’s consider a scenario where a registration form is needed in multiple parts of your app.

## For text Input :

```
const InputText = ({
    labelTitle,
    type,
    register,
    fieldName,
    require,
    errors,
    defaultValue,
    disabled,
    maxLength
}: any) => {
    return (
        <div>
            <div className="relative">
                <input
                    defaultValue={defaultValue ? defaultValue : ''}
                    disabled={disabled}
                    type={type}
                    id={fieldName}
                    {...register(fieldName, {
                        required: require || false,
                        maxLength: maxLength || 400,
                        pattern: {
                            value: /^[^\s]+(?:$|.*[^\s]+$)/,
                            message: `Please enter valid ${labelTitle}`
                        }
                    })}
                    className="block px-2.5 py-2.5  w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none  focus:outline-none focus:ring-0 focus:border-indigo-600 peer"
                    placeholder=" "
                />
                <label
                    htmlFor={fieldName}
                    className="absolute text-base text-gray-500  duration-300 transform -translate-y-4 scale-75 top-2 z-0 origin-[0] bg-white  px-2 peer-focus:px-2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
                >
                    {labelTitle}
                    {require && <span className="text-base text-red-500">*</span>}
                </label>
            </div>
            {errors && errors?.[`${fieldName}`]?.type === 'required' && (
                <p className="text-xs text-red-500" role="alert">
                    {labelTitle} is required
                </p>
            )}
 
            {errors?.[`${fieldName}`] && (
                <p role="alert" className="text-xs text-red-500">
                    {errors?.[`${fieldName}`]?.message}
                </p>
            )}
            {errors?.[`${fieldName}`]?.type === 'maxLength' && (
                <p role="alert" className="text-red-500 text-xs">
                    {labelTitle} can&#39;t exceed {maxLength} characters
                </p>
            )}
        </div>
    )
}
 
export default InputText
```

## For Select Input :

```
function SelectBox({
    labelTitle,
    options,
    register,
    fieldName,
    require,
    errors
}: any) {
    return (
        <>
            <div className="relative">
                <select
                    id={fieldName}
                    {...register(fieldName, {
                        required: require || false
                    })}
                    className="block px-2.5 py-2 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-indigo-600 peer"
                    defaultValue=""
                >
                    {options.map((item: any, index: any) => (
                        <option key={index} value={item.value}>
                            {item.name}
                        </option>
                    ))}
                </select>
                <label
                    htmlFor={fieldName}
                    className="absolute text-sm text-gray-500 duration-300 transform -translate-y-4 scale-75 top-2 z-0 origin-[0] bg-white px-2 peer-focus:px-2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
                >
                    {labelTitle}
                    {require && <span className="text-base text-red-500">*</span>}
                </label>
            </div>
            {errors && errors?.[`${fieldName}`]?.type === 'required' && (
                <p className="text-xs text-red-500" role="alert">
                    {labelTitle} is required
                </p>
            )}
        </>
    )
}
 
export default SelectBox
```

## For Textarea Input :

```
function TextAreaInput({
    labelTitle,
    type,
    register,
    fieldName,
    require,
    errors,
    maxLength,
    defaultValue
}: any) {
    return (
        <>
            <div className="relative">
                <textarea
                    defaultValue={defaultValue ? defaultValue : ''}
                    type={type}
                    id={fieldName}
                    {...register(fieldName, {
                        required: require || false,
                        maxLength: maxLength || 2000,
                        pattern: {
                            value: /^[^\s]+(?:$|.*[^\s]+$)/,
                            message: `Please enter valid ${labelTitle}`
                        }
                    })}
                    rows={4}
                    className="block px-2.5 py-2 w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none focus:outline-none focus:ring-0 focus:border-indigo-600 peer"
                    placeholder=" "
                ></textarea>
                <label
                    htmlFor={fieldName}
                    className="absolute text-base text-gray-500  duration-300 transform -translate-y-4 scale-75 top-2 z-0 origin-[0] bg-white  px-2 peer-focus:px-2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
                >
                    {labelTitle}
                    {require && <span className="text-base text-red-500">*</span>}
                </label>
            </div>
            {errors?.[`${fieldName}`]?.type === 'required' && (
                <p role="alert" className="text-xs text-red-500">
                    {labelTitle} is required
                </p>
            )}
            {errors?.[`${fieldName}`]?.type === 'maxLength' && (
                <p role="alert" className="text-red-500 text-xs">
                    {labelTitle} can&#39;t exceed {maxLength} characters
                </p>
            )}
            {errors?.[`${fieldName}`] && (
                <p role="alert" className="text-xs text-red-500">
                    {errors?.[`${fieldName}`].message}
                </p>
            )}
        </>
    )
}
 
export default TextAreaInput
```

## For Password Input :

```
const InputPassword = ({
    labelTitle,
    type,
    register,
    fieldName,
    require,
    errors,
    defaultValue,
    disabled,
    maxLength
}: any) => {
    return (
        <div>
            <div className="relative">
                <input
                    defaultValue={defaultValue ? defaultValue : ''}
                    disabled={disabled}
                    type={type}
                    id={fieldName}
                    {...register(fieldName, {
                        required: require || false,
                        maxLength: maxLength || 400,
                        pattern: {
                            value: /^[^\s]+(?:$|.*[^\s]+$)/,
                            message: `Please enter valid ${labelTitle}`
                        },
                        minLength: {
                            value: 8,
                            message: 'Password must be at least 8 characters long'
                        }
                    })}
                    className="block px-2.5 py-2.5  w-full text-sm text-gray-900 bg-transparent rounded-lg border-1 border-gray-300 appearance-none  focus:outline-none focus:ring-0 focus:border-indigo-600 peer"
                    placeholder=" "
                />
                <label
                    htmlFor={fieldName}
                    className="absolute text-base text-gray-500  duration-300 transform -translate-y-4 scale-75 top-2 z-0 origin-[0] bg-white  px-2 peer-focus:px-2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1"
                >
                    {labelTitle}
                    {require && <span className="text-base text-red-500">*</span>}
                </label>
            </div>
            {errors && errors?.[`${fieldName}`]?.type === 'required' && (
                <p className="text-xs text-red-500" role="alert">
                    {labelTitle} is required
                </p>
            )}
 
            {errors?.[`${fieldName}`] && (
                <p role="alert" className="text-xs text-red-500">
                    {errors?.[`${fieldName}`]?.message}
                </p>
            )}
            {errors?.[`${fieldName}`]?.type === 'maxLength' && (
                <p role="alert" className="text-red-500 text-xs">
                    {labelTitle} can&#39;t exceed {maxLength} characters
                </p>
            )}
        </div>
    )
}
 
export default InputPassword
```

## Parents Like :

```
<InputText
                                type="type"
                                labelTitle="name"
                                register={register}
                                fieldName="name"
                                errors={errors}
                                require={true}
                            />
 
<TextAreaInput
                    labelTitle="name"
                    register={register}
                    fieldName="name"
                    errors={errors}
                    require={true}
                    maxLength="2000"
                />
 
 
const Options = [
    { name: 'Select Medium', value: '' },
    { name: 'Online', value: 'Online' },
    { name: 'Offline', value: 'Offline' }
]
 
<SelectBox
                        options={Options}
                        labelTitle="name"
                        register={register}
                        fieldName="name"
                        errors={errors}
                        require={true}
                    />
 
<InputPassword
                        type="password"
                        labelTitle="name"
                        register={register}
                        fieldName="password"
                        errors={errors}
                        require={true}
                    />
```
**Building the Best Form for React and Next.js**
When it comes to crafting the best form for React and Next.js applications, React Hook Form stands out for several reasons. Its lightweight nature ensures minimal impact on performance, and its uncontrolled form approach reduces the need for re-rendering, resulting in a smoother user experience.

**Avoiding Unnecessary Re-renders**
React Hook Form excels in optimizing re-renders by leveraging uncontrolled components. The library utilizes refs to register form inputs, allowing changes to be tracked without causing unnecessary re-renders of the entire form. This is especially crucial for complex forms or applications with a high level of interactivity.

**TypeScript for Robustness**
Integrating TypeScript with React Hook Form adds an extra layer of robustness to your forms. By defining interfaces for form data, you ensure type safety and catch potential errors during development rather than at runtime. This enhances code maintainability and makes refactoring a breeze.

In conclusion, React Hook Form, when combined with TypeScript, provides a robust and efficient solution for building reusable forms in React and Next.js applications. By encapsulating form logic into reusable components, leveraging uncontrolled components, and embracing TypeScript for type safety, developers can create forms that not only meet the highest standards of performance but also contribute to a more maintainable codebase. Adopting these best practices ensures that your forms not only look good but also perform optimally, providing users with a seamless and delightful experience.



If need any website, web app, UI design, or branding, Then feel free to contact  - [oxylx](https://oxylx.com/)
Portfolio - [Sourav Kairy](https://www.souravkairy.com/)