Reputation: 1317
Not sure what this is called, but let's say I got these interfaces
interface Dog {
type: "dog"
canBark: boolean
}
interface Cat {
type: "cat"
canMeow: boolean
}
I would like to be able to say: If type="Dog"
, there should be a canBark
property (as a boolean) and no other properties. If type="Cat"
, there should be a canMeow
property (as a boolean) and no other properties.
An example React code would look like this:
export default function SomeReactComponent({ type, ...other }: Cat | Dog) {
switch (type) {
case "dog":
return <DogComponent {...other} />
case "cat":
return <CatComponent {...other} />
default:
throw new Error("Unknown animal type.")
}
}
function DogComponent({ canBark }: { canBark: boolean }) {}
function CatComponent({ canMeow }: { canBark: boolean }) {}
However, I get this error on DogComponent
:
Type '{ canBark: boolean; } | { canMeow: boolean; }' is not assignable to type 'IntrinsicAttributes & { canBark: boolean; }'.
Property 'canBark' is missing in type '{ canMeow: boolean; }' but required in type '{ canBark: boolean; }'.ts(2322)
and this error on CatComponent
:
Type '{ canBark: boolean; } | { canMeow: boolean; }' is not assignable to type 'IntrinsicAttributes & { canBark: boolean; }'.
Property 'canBark' is missing in type '{ canMeow: boolean; }' but required in type '{ canBark: boolean; }'.ts(2322)
When I create a Animal
interface with all possible values, it works:
interface Animal {
type: "dog" | "cat"
canBark: boolean | undefined
canMeow: boolean | undefined
}
interface Dog extends Animal {
type: "dog"
canBark: boolean
}
interface Cat extends Animal {
type: "cat"
canMeow: boolean
}
However, it's a bit tedious to write these base classes for every React component, is there a way to accomplish this without writing an Animal
class?
Upvotes: 2
Views: 45
Reputation: 249786
Typescript is not smart enough to follow the fact that type
and other
are actually entangled. There is an open issue about this one that might see improvement, as Typescript has just recently (in 4.6) added support for narrowing such entangled types with this PR
One simple option is to not de-structure the props in the parameters, narrow them first, and then pass them directly to the component or de-structure the type
prop out:
export default function SomeReactComponent(o: Cat | Dog) {
switch (o.type) {
case "dog":
return <DogComponent {...o} />
case "cat":
return <CatComponent {...o} />
default:
throw new Error("Unknown animal type.")
}
}
You can also destructure after the narrowing:
export default function SomeReactComponent(o: Cat | Dog) {
switch (o.type) {
case "dog": {
const { type, ...other } = o
return <DogComponent {...other} />
}
case "cat": {
const { type, ...other } = o
return <CatComponent {...other} />
}
default:
throw new Error("Unknown animal type.")
}
}
Upvotes: 2