I have code that is something like this
// some package
export interface TCustomer {
name: string;
}
import { TCustomer } from "some-package"
interface BCustomer extends TCustomer {
options: string;
}
type Props = {
customers: TCustomer[] | BCustomer[];
};
const CustomersTable: React.FC<Props> = ({ customers }) => {
return customers.map(customer => (
<div>
{customer.options && <span>{customer.name}</span>}
<span>{customer.name}</span>
</div>
)
)}
Typescript doesn't like that customer.options
doesn't exist on the imported type of TCustomer
. That makes sense to me and is true. What I don't understand is why? I'm handling the "type" by using &&
so my intuition is that TS should see that I know that this "might" be undefined.
What is typescript trying to enforce here, and how can I write an if statement on a union type in a more Typescript way?
I'm aware that I "could" write:
interface BCustomer extends TCustomer {
options: string;
}
interface CCustomer extends TCustomer {
options?: undefined;
}
type Props = {
customers: CCustomer[] | BCustomer[];
};
But this seems unneccesary when I could handle the this internally to the component. It seems odd to add a field to a type that shouldn't have that field. However, this may be the "correct" way to do it. If that's the case that's fine. If that's the case I would like to understand why TS is this way so that I can write better TS.
CodePudding user response:
You can't directly access a property if TypeScript can't determine that the property definitely exists. Even if you're only accessing it to check for truthyness, it's not allowed, unfortunately. Here, you can use in
instead. (Using in
is not only permitted, it'll also let TS narrow the type for use later - while not useful here since you aren't using any BCustomer
-specific properties, it's a good technique to know.)
{'options' in customer && <span>{customer.name}</span>}
Since you only need JavaScript logic and not TypeScript narrowing here, you could also use
{customer.hasOwnProperty('options') && <span>{customer.name}</span>}
Even if {customer.options &&
was permitted, it might be a mistake, because not all customers with a falsey options
will be a TCustomer
- it might be a BCustomer
with an empty string.