React use different interface in dependent on property value

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?

Example


Solution 1:

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)

Playground link