Home > OS >  React use different interface in dependent on property value
React use different interface in dependent on property value

Time:01-23

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

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)

Playground link

  •  Tags:  
  • Related