Create a new form field
Why?
Allows to integrate a new type of field with React Hook Form.
How? Create a new FormField type (recommended)
Define your Field
In the src/components/Form
directory, create a new folder FieldYourType
with an index.tsx
file.
src/components/Form/FieldYourType/index.tsx
import { Controller, FieldPath, FieldValues } from "react-hook-form";
import { CustomComponent } from "@/components/CustomComponent";
import { FieldCommonProps } from "@/components/Form/FormFieldController";
import { FormFieldError } from "@/components/Form/FormFieldError";
export type FieldYourTypeProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
type: "your-type";
} & FieldCommonProps<TFieldValues, TName>;
export const FieldYourType = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>(
props: FieldYourTypeProps<TFieldValues, TName>
) => {
return (
<Controller
{...props}
render={({ field }) => (
<>
<CustomComponent {...field} />
<FormFieldError />
</>
)}
/>
);
};
Declare your Field in the FormField file
In the src/components/Form/FormField.tsx
file, add the props and the component for your type.
src/components/Form/FormFieldController.tsx
/* ... */
import { FieldYourType, FieldYourTypeProps } from "./FieldYourType";
/* ... */
export type FormFieldProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> =
| FieldCustomProps<TFieldValues, TName>
// -- ADD NEW FIELD PROPS TYPE HERE --
| FieldYourTypeProps<TFieldValues, TName>
| FieldTextProps<TFieldValues, TName>
/* ... */
| FieldRadiosProps<TFieldValues, TName>;
export const FormFieldController = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>(
_props: FormFieldControllerProps<TFieldValues, TName>
) => {
/* ...*/
const getField = () => {
switch (props.type) {
case "custom":
return <Controller {...props} />;
case "text":
case "email":
case "number":
case "tel":
return <FieldText {...props} />;
/* ... */
case "your-type":
return <FieldYourType {...props} />;
// -- ADD NEW FIELD COMPONENT HERE --
}
};
/* ... */
};
Use your new Field in a form
<Form {...form}>
<FormField>
<FormFieldLabel>Field Label</FormFieldLabel>
<FormFieldController
control={form.control}
type="your-type"
name="field-name"
/>
</FormField>
</Form>
Storybook and tests
It is good practice to create docs.stories.tsx
and FieldYourType.spec.tsx
files in the src/components/Form/FieldYourType
folder for documenting and testing your new field type.
How? Use the FormField custom
type
Use the custom
type of the FormField component to use an abitrary component on the fly. If you need a reusable component, take the Create a new FormField type approach.
<Form {...form}>
<FormField>
<FormFieldLabel>Field Label</FormFieldLabel>
<FormFieldController
control={form.control}
type="custom"
name="field-name"
render={({ field }) => (
<>
<CustomComponent {...field} />
<FormFieldError />
</>
)}
/>
</FormField>
</Form>