I have 3 different types of items, which i have interfaces of
interface TypeA{
name: string;
}
interface TypeB{
count: number;
}
interface TypeC{
date: Date;
}
This items should be rendered in a list of items (in a list all items are from the same type). Depending on the type of the item, a different method is called, which will render different layouts.
export const ListItem: React.FC<ListItemProps> = (props) => {
let item = null;
switch (props.type) {
case "A":
item = renderTypeA(props);
break;
case "B":
item = renderTypeB(props);
break;
case "C":
item = renderTypeC(props);
break;
}
return item;
};
The method should accept just items from the desired type.
const renderTypeA = (props: TypeAProps) => {
{...}
};
The problem is that I can't get Typescript to recognize all properties of the types and also only auto-complete the respective types.
I have also tried it with a union type "ListItemTypes",
type ListItemTypes = TypeA | TypeB | TypeC
export const ListItem: React.FC<ListItemTypes> = (props) => {
...
};
but when I then try to include the ListItem, I always get an error that properties are missing.
<ListItem {...item /> <--- is telling me that properties are missing
Does anyone know how I can fix this problem?
CodePudding user response:
Here is a minimal example of discriminated union usage:
interface TypeA {
type: "A";
name: string;
}
interface TypeB {
type:"B";
count: number;
}
export const ListItem = (props: TypeA | TypeB) => {
let item = null;
switch (props.type) {
case "A":
item = renderTypeA(props); // TS know this must be TypeA in this block
break;
case "B":
item = renderTypeB(props);
break;
}
return item;
};
const renderTypeA = (props: TypeA) => {
props.name; // No error
};
const renderTypeB = (props: TypeB) => {
props.name; // Error
};
// Update:
const renderItem = () => {
const sampleData: Array<ListItemTypes> = [
{
type: "A",
count: 12, // This doesn't follow your ListItemTypes shape
},
];
// ListItem requires an object, not a list of objects
return sampleData.map(sd => <ListItem {...sd} />);
};
CodePudding user response:
I checked your code, it's throwing errors because you're destructuring an array into your component. In your code
const renderItem = () => {
const sampleData = [
{
type: "A",
count: 12,
},
];
// incorrectly destructuring sampleData here
return <ListItem {...sampleData} />;
};
You should be able to achieve the expected result with this instead:
const renderItemA = () => {
const sampleData = {
type: "A" as const, // notice const assertion here
name: "name"
};
return <ListItem {...sampleData} />;
};
Because you defined type as literal type, you need const assertion so that
no literal types in that expression should be widened (e.g. no going from "hello" to string)
