Reputation: 4122
Why it does not work, when I am checking if the phone
exists or not. The TypeScript should catch that and know if phone
exists, then it must be AddressWithPhone
interface.
Its been so long, I probably did not define the types correctly,
interface Address {
street: string;
zip: string;
state: string;
city: string;
}
interface AddressWithPhone extends Address {
phone: string;
}
interface EditAddressProps<T = Address | AddressWithPhone> {
address: T;
onChange: (address: T) => unknown;
}
const something = <T,>(props: EditAddressProps<T>): string => {
// Error: Property 'phone' does not exist on type 'T'
if (props.address.phone) {
return 'Its AddressWithPhone';
}
return 'Its Address without phone';
}
TS Playground:
Upvotes: 2
Views: 5384
Reputation: 33051
Here is alternative way to do it:
type AddressBase = {
street: string;
zip: string;
state: string;
city: string;
};
type Address =
| AddressBase
| AddressBase & {
phone: string;
}
type EditAddressProps<T> = {
address: T;
onChange: (address: T) => unknown;
}
const withPhone = (props: EditAddressProps<any>): props is EditAddressProps<AddressBase & {
phone: string;
}> => Object.prototype.hasOwnProperty.call(props.address, 'phone');
const withoutPhone = (props: EditAddressProps<any>): props is EditAddressProps<AddressBase> =>
!withPhone(props)
const something = < T extends Address>(props: EditAddressProps<T>) => {
if (withPhone(props)) {
const phone = props.address.phone; // string
props.onChange({
street: '123',
zip: 'AB12345',
state: 'TX',
city: 'Dallas',
phone: 'sdf', // phone is required here
})
return 'hello'
}
if (withoutPhone(props)) {
const x = props.address // no phone
props.onChange({
street: '123',
zip: 'AB12345',
state: 'TX',
city: 'Dallas',
phone: 'sdf' // expected error, phone property is redundant here
})
}
const defaultCase = props; // EditAddressProps<T>
return 'default'
}
If you have a deal with union types with generics, I would suggest to use typeguards, because TS is unable to narrow it as you would expect
Upvotes: 1
Reputation: 42188
When you do <T = Address | AddressWithPhone>
you are setting the default value of T
to the union Address | AddressWithPhone
. You haven't actually constrained the possible values of T
so T
still could be anything.
You want to use extends
to limit the possible values of T
. This still doesn't work perfectly with a union because phone
is not defined on Address
, and properties must be defined on all members of a union in order to access them. We want a base type with all properties of Address
required and phone
optional. There's a lot of ways to define that, but here's one:
type BaseAddress = Address & Partial<AddressWithPhone>
const something = <T extends BaseAddress>(props: EditAddressProps<T>): string => {
Upvotes: 3