最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

typescript - Best practice for typing forms with more than one field type - Stack Overflow

programmeradmin0浏览0评论

I'm using RHF and trying to create a custom form component that takes an array of Fields and then renders an input for each Field. For example, it might take as input something like this:

const fields = [
    {
      type: 'select',
      name: 'data',
      label: 'Location/Date',
      options: [
        { name: 'a', color: 'blue' },
        { name: 'b', color: 'green' },
        { name: 'c', color: 'purple' },
      ],
      getDisplayText: (item) =>
        ,
    },
    {
      type: 'text',
      name: 'harvestSchemaId',
      label: 'Harvest Schema ID',
      validation: {
        required: 'Harvest Schema ID is required',
      },
    },
    {
      type: 'text',
      name: 'pinId',
      label: 'Pin ID',
      validation: {
        required: 'Pin ID is required',
      },
    },
  ]

Then the component maps over all of the inputs and renders either an or , based on field.type. The issue I'm running into is that my select type takes generic options:

interface BaseField {
  name: string
  label?: string
  validation?: RegisterOptions
}

interface InputField extends BaseField {
  type: 'text' | 'email' | 'password'
}

interface SelectField<T> extends BaseField {
  type: 'select'
  options: T[]
  getDisplayText?: (item: T) => string
}

Because the getDisplayText callback needs to have the same type as options. My question is, how in the world can I type everything such that I have type safety? I've tried typing Fields like this:

const fields: Field<{ name: string; color: string }>[] = ...

But this only works if there's one select. If there's more than one, I have to do a union:

const fields: Field<{ name: string; color: string } | { test: string }>[] = ...

And then I lose all type safety for my getDisplayText callback. Also, I know I could just do it like this:

const fields: [
    SelectField<{ name: string; color: string }>,
    InputField,
    InputField,
  ] = ...

But I feel like there's got to be a better way, and I'm trying to make this as reusable and non-boilerplatey as possible. What's a good way to do this? I know this has got to be a pretty common use case, but I can't find anything online. Thanks for any help!

I'm using RHF and trying to create a custom form component that takes an array of Fields and then renders an input for each Field. For example, it might take as input something like this:

const fields = [
    {
      type: 'select',
      name: 'data',
      label: 'Location/Date',
      options: [
        { name: 'a', color: 'blue' },
        { name: 'b', color: 'green' },
        { name: 'c', color: 'purple' },
      ],
      getDisplayText: (item) =>
        ,
    },
    {
      type: 'text',
      name: 'harvestSchemaId',
      label: 'Harvest Schema ID',
      validation: {
        required: 'Harvest Schema ID is required',
      },
    },
    {
      type: 'text',
      name: 'pinId',
      label: 'Pin ID',
      validation: {
        required: 'Pin ID is required',
      },
    },
  ]

Then the component maps over all of the inputs and renders either an or , based on field.type. The issue I'm running into is that my select type takes generic options:

interface BaseField {
  name: string
  label?: string
  validation?: RegisterOptions
}

interface InputField extends BaseField {
  type: 'text' | 'email' | 'password'
}

interface SelectField<T> extends BaseField {
  type: 'select'
  options: T[]
  getDisplayText?: (item: T) => string
}

Because the getDisplayText callback needs to have the same type as options. My question is, how in the world can I type everything such that I have type safety? I've tried typing Fields like this:

const fields: Field<{ name: string; color: string }>[] = ...

But this only works if there's one select. If there's more than one, I have to do a union:

const fields: Field<{ name: string; color: string } | { test: string }>[] = ...

And then I lose all type safety for my getDisplayText callback. Also, I know I could just do it like this:

const fields: [
    SelectField<{ name: string; color: string }>,
    InputField,
    InputField,
  ] = ...

But I feel like there's got to be a better way, and I'm trying to make this as reusable and non-boilerplatey as possible. What's a good way to do this? I know this has got to be a pretty common use case, but I can't find anything online. Thanks for any help!

Share Improve this question asked Nov 20, 2024 at 0:43 GMoneyGMoney 435 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

One way I have solved this problem is defining a no-op helper function for type checking.

function createField(field: InputField): BaseField;
function createField<T>(field: SelectField<T>): BaseField;
function createField(field: BaseField) {
    return field;
}

createField is a function with multiple overloads for each of the BaseField types you have, and it will just return the same object, with type BaseField. It will force typescript to type-check its input, to actually be an InputField or a SelectField<T>.

Then you can use it like this:

const fields: BaseField[] = [
    createField({
        type: 'select',
        name: 'data',
        label: 'Location/Date',
        options: [
            { name: 'a', color: 'blue' },
            { name: 'b', color: 'green' },
            { name: 'c', color: 'purple' },
        ],
        getDisplayText: (item) => item.name
    }),
    createField({
        type: 'text',
        name: 'harvestSchemaId',
        label: 'Harvest Schema ID',
        validation: {
            required: 'Harvest Schema ID is required',
        },
    }),
    createField({
        type: 'text',
        name: 'pinId',
        label: 'Pin ID',
        validation: {
            required: 'Pin ID is required',
        },
    }),
];
发布评论

评论列表(0)

  1. 暂无评论