Home > OS >  How do I make a prop optional if another prop is of a specified type?
How do I make a prop optional if another prop is of a specified type?

Time:08-20

I want the transform prop of this react component to be optional if the generic type argument is an Option

Therefore I define them like this.

type Option = { value: string; label: string }
type TransformFn<T> = (item: T) => Option

type MaybeOptionalTransform<T> = T extends Option
  ? { transform?: TransformFn<T> }
  : { transform: TransformFn<T> }

type SuspendedPromise<T> = { read: () => T}

type SuspendedSelectProps<T> = {
  resource: SuspendedPromise<T[]>
  onChange?: (value: string) => void
  value?: string
} & MaybeOptionalTransform<T>


function SuspendedSelect<T>(props: SuspendedSelectProps<T>) {
  return null
}

Using this component as a "function" works:

SuspendedSelect({
  resource: { read: () => [{ foo: 'aaa', bar: 'bbb' }] },
  onChange: value => undefined,
  transform: item => ({ label: item.foo, value: item.bar })
})

SuspendedSelect({
  resource: { read: () => [{ label: 'aaa', value: 'bbb' }] },
  onChange: value => undefined
})

Both these invocations works as expected, typescript gives no error.

But when I use it as a react component like this:

<SuspendedSelect
  resource={{ read: () => [{ foo: 'aaa', bar: 'bbb' }] }}
  onChange={value => undefined}
  transform={item => ({ label: item.foo, value: item.bar })}
/>

I get a typescript error:

TS2322: Type '(item: Option & { foo: string; bar: string; }) => { label: string; value: string; }'
        is not assignable to type 'TransformFn<{ foo: string; bar: string; }>'.
  Types of parameters 'item' and 'item' are incompatible.
     Type '{ foo: string; bar: string; }' is not assignable to type
          'Option & { foo: string; bar: string; }'.
       Type '{ foo: string; bar: string; }' is missing the following properties from type 
           'Option': value, label

  deviation-modal.tsx(23, 7): The expected type comes from property 'transform' which is
  declared here on type 
  'IntrinsicAttributes
   & {
       resource: { read(): { foo: string; bar: string; }[]; };
       onChange?: ((value: string) => void) | undefined; value?: string | undefined;
     }
   & { transform: TransformFn<...>; }'

It goes away if I manually specify the type when I render the component. But I would prefer if typescript could infer the correct type by itself.

Why does typescript infer the 'incorrect' type for the transform function? And can I do anything about it?

CodePudding user response:

In order to make it work, you need to overload your component function instead of making conditional typings. Please keep in mind, that react component is just a regular function.

import React from 'react'

type Option = { value: string; label: string }
type TransformFn<T> = (item: T) => Option

type SuspendedPromise<T> = { read: () => T }

type SuspendedSelectProps<T> = {
  resource: SuspendedPromise<T[]>
  onChange?: (value: string) => void
  value?: string
}

function SuspendedSelect<T>(props: SuspendedSelectProps<T> & { transform: TransformFn<T> }): any
function SuspendedSelect<T extends Option>(props: SuspendedSelectProps<T> & { transform?: TransformFn<T> }): any
function SuspendedSelect<T,>(props: SuspendedSelectProps<T>) {
  return null
}

SuspendedSelect({
  resource: { read: () => [{ foo: 'aaa', bar: 'bbb' }] },
  onChange: value => undefined,
  transform: item => ({ label: item.foo, value: item.bar })
})

SuspendedSelect({
  resource: { read: () => [{ label: 'aaa', value: 'bbb' }] },
  onChange: value => undefined
})

const jsx = <SuspendedSelect
  resource={{ read: () => [{ foo: 'aaa', bar: 'bbb' }] }}
  onChange={value => undefined}
  transform={item => ({ label: item.foo, value: item.bar })}
/>

Playground

Also, you can check my articles: my blog and dev.to

  • Related