I would like to find out if anyone has any good solutions to the below situation. I am trying to specifically have an optional property on my component dependent upon the type passed in. If the data type is NOT of a particular shape, then the property becomes required. Please see the example below :)
type TestProps<T> = ({
data: T,
onChange: (value: T) => void,
} & (T extends { label: string, value: string } ? { getValues?: (val: T) => number } : { getValues: (val: T) => string }));
function MyComponent<T>(props: TestProps<T>) {
return <div></div>;
}
function Wrapper<T>(rest: TestProps<T>) {
//Test 1: Should not display an error
return <MyComponent {...rest} />;
}
type HaloObj = {
code: string,
name: string,
}
function TestImpl() {
const normalObj = { label: 'label', value: 'value' };
const customObj: HaloObj = { code: 'HAL', name: 'Halo Trilogy' };
return (
<>
{/* Test 2: Should allow omitting the prop with no errors*/}
<MyComponent
data={normalObj}
onChange={(value) => value}
// getValues={(val) => 1}
></MyComponent>
{/* Test 3: No Errors */}
<MyComponent
data={normalObj}
onChange={(value) => value}
getValues={(val) => 1}
></MyComponent>
{/* Test 4: Should display an error required prop missing */}
<MyComponent
data={customObj}
onChange={(value) => value}
// getValues={(val) => val.code + val.name + "Hello World"}
></MyComponent>
{/* Test 5: No errors, getValues typed properly*/}
<MyComponent
data={customObj}
onChange={(value) => value}
getValues={(val) => val.code + val.name + "Hello World"}
></MyComponent>
</>
)
}
The error received near Test 1, when wrapping the component, is:
Type 'TestProps<T>' is not assignable to type 'IntrinsicAttributes'.ts(2322)
Althought the wrapper doesn't work the TestImpl function, and Test 2,3,4,5 all work correctly. The typing for required/optional props works as expected.
I have tried adding [] around the T in line 4.
type TestProps<T> = ({
data: T,
onChange: (value: T) => void,
} & ([T] extends { label: string, value: string } ? { getValues?: (val: T) => number } : { getValues: (val: T) => string }));
This achieves the success in removing the error when wrapping the component. However, it then causes some of the other tests to fail specifically:
Test 2 with error:
Property 'getValues' is missing in type '{ data: { label: string; value: string; }; onChange: (value: { label: string; value: string; }) => { label: string; value: string; }; }' but required in type '{ getValues: (val: { label: string; value: string; }) => string; }'.ts(2741)
test.tsx(4, 92): 'getValues' is declared here.
Test 3 with error:
Type 'number' is not assignable to type 'string'.ts(2322)
test.tsx(4, 103): The expected type comes from the return type of this signature.
It seems to me like the square brace (some part of Distributive Conditional Types) Only ends in the second 'getValues' type being used. Any help would be appreciated :)
Type 'TestProps<T>' is not assignable to type 'IntrinsicAttributes &
{ data: T; onChange: (value: T) => void; } & (T extends { label: string; value: string; }
? { getValues?: ((val: T) => number) | undefined; } : { ...; })'.
Type 'TestProps<T>' is not assignable to type 'IntrinsicAttributes'.typescript(2322)
This is saying that IntrinsicAttributes
is missing from TestProps<T>
. Note the IntrinsicAttributes &
and then the rest of your TestProps<T>
type definition.
IntrinsicAttributes
is the base of JSX props React components use.
interface IntrinsicAttributes extends React.Attributes {}
interface Attributes { key?: Key | null | undefined; }
Update the Wrapper
component props to extend the IntrinsicAttributes
interface.
Example:
type WrapperProps<T> = JSX.IntrinsicAttributes & TestProps<T>;
function Wrapper<T>(rest: WrapperProps<T>) {
return <MyComponent {...rest} />;
}
Test case 4 is now the only one displaying issue, correctly as expected.
This can also be resolved by updating the Wrapper
component interface to limit T
to objects.
function Wrapper<T extends {}>(rest: TestProps<T>) {
//Test 1: Should not display an error
return <MyComponent {...rest} />;
}