Guides
Create a new form field

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>