Create an input component that combines React Hook Form with MUI, react-number-format

Posted on: 4/23/2022

Introduction

I tried to create an input component that can be used universally by combining React Hook Form, which has become a standard in the recent React form libraries, and various input components of MUI, which is also commonly used in React projects. In the text input, it is always necessary to separate pure text (string type) input from numeric (number type) input, so based on the MUI TextField, I prepared two input components, MuiTextField and MuiNumericField, to distinguish between them. Also, since there are many situations where you want to format and display input values in numeric input parts, I also incorporated react-number-format into MuiNumericField so that you can format the input values.

Dependent version at the time of writing (last updated on Nov. 03, 2024)

  • React v18.2.0
  • MUI v6.1.6
  • React Hook Form v7.53.1
  • react-number-format v5.4.2

I'll post the final demo first. (Sorry for the Japanese sample.)

GitHub repository is below. (Commented in English.)

List of currently available parts.

From here, we will summarize the philosophy behind the interface design, the information we obtained for each library while creating this component, and the points that stuck out in the implementation. If you want to use the generic input component implemented here, you can have a quick look at the "Interface from the User's Point of View" to get an idea of how to use it, and then look at the sample form implementation on GitHub.

Interfaces

Interface from the User's Point of View

To introduce the interface, we will show you how to use the simplest MuiTextField (a text input component of type string). The other components except for MuiTextField have the same interface design, so if you know MuiTextField, you can use the other components easily.

The component now takes the type of the form object as its type argument. The type of the form is always defined (and passed to the type argument of useForm) when using React Hook Form in TypeScript, so you can pass the same type as it is. If you pass the type here, it will be checked if the string you pass to the name props of the input component exists as a key of the form.

Example of MuiTextField use
<MuiTextField<FormData, 'username'> name='username' control={control} rules={{ required: 'Enter your username.', maxLength: { value: 10, message: 'Enter up to 10 characters.' } }} config={{ displayErrorMessage: true }} muiProps={{ textFieldProps: { label: 'Username', fullWidth: true } }} />

Props can be divided into three main categories.

  • Props defined in React Hook Form. We will use this as the base interface for input components.
  • Props defined for MUI components. This time, we will group them as muiProps.
  • Additional configuration items for the part provided by the part provider. We will summarize this as config.

In the example above, name, control, and rules are the interfaces for the React Hook Form. In this generic part, since this is the base of the design, we can pass them as non-nested props. Among them, control and name are required for control by React Hook Form, so they are also required as Props. On the other hand, rules is not required, and validation can be realized by defining a form schema using a schema library such as Zod and passing the schema to useForm. Of course, it is also possible without validation.

Interface from the Provider's Point of View

Since there are a lot of Proposals, I decided to divide them into three major groups to manage them. The first is the React Hook Form Prop, which is the main component of this project, the second is the MUI Component Prop, and the third is the Prop to provide options that the component provider wants to incorporate on their own.

Props of React Hook Form

The interface of React Hook Form is provided by the library as UseControllerProps. Since I want this Props to be on the top surface as an interface (I don't want it to be nested), I decided to make the Props of the part I'm going to create an intersection type with this type. In the above sample, name, control, and rules are Props defined in the interface exposed by React Hook Form.

Props for MUI Component

Some parts require multiple MUI props. For example, in Autocomplete, in addition to Autocomplete's own Props, we want to be able to pass the internal TextField's Props (form width, label, etc.). Therefore, we organized them into objects, and made it possible to pass them through a mouth called muiProps of Props. Therefore, we have eliminated the possibility of creating bugs by separating them from the beginning.

Configuration items provided independently by component providers

Props to provide options that providers want to incorporate into their own components are also grouped into an object and can be passed as config, just like props in the MUI. (NumberFormatPropsBase) is semantically better to fit here, so the MuiNumberField config is crossed with NumberFormatPropsBase.