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 })}
/>