uday8486
uday8486

Reputation: 1393

Typescript iterating over object with union type

I need to loop through an object which is of type union of two interfaces.

    interface Family {
      cat: string;
      age: string;
      family: string;
      lastYearFamily: string;
    }
    
    interface Model {
      cat: string;
      age: string;
      model: string;
      lastYearModel: string;
    }
    
    interface Props {
        attributionType: 'family' | 'model';
        attributions?: Family[] | Model[];
    }
    
    const RowComponent = ({
    attributionType,
    attributions
    }: props) =>{
    
       return (
          {attributions && attributions.map((attribution: Family | Model ) => (
                             <tr>
                                {
                                    attributionType === 'family' && (
                                        <React.Fragment>
                                            <th>{attribution.family}</th>
                                            <th>Family</th>
                                        </React.Fragment>
                                    )
                                } 
                                {
                                    attributionType === 'model' && (
                                        <React.Fragment>
                                            <th>{attribution.model}</th>
                                            <th>Model</th>
                                        </React.Fragment>
                                    )
                                } 
                             </tr>
                        ))}
    );
    
    }

Since this is a union I am not able to access any other members of the object which are not common.

I can access cat and age but not family, lastYearFamily etc.

I want to keep the code common if possible and not have separate components for each attribution type.

Upvotes: 0

Views: 1409

Answers (2)

Chris Heald
Chris Heald

Reputation: 62668

You would typically do this with custom type guards, which consists of casting a variable as a prospective type and checking if a field unique to that type exists, which permits you to infer that the value in the variable is indeed of that type.

interface Family {
      cat: string;
      age: string;
      family: string;
      lastYearFamily: string;
}
    
interface Model {
      cat: string;
      age: string;
      model: string;
      lastYearModel: string;
}

const isFamily = (f: Family|Model): f is Family => {
    return (f as Family).family !== undefined
}

const isModel = (f: Family|Model): f is Model => {
    return (f as Model).model !== undefined
}

const x : (Family | Model)[] = [
    {cat: "x", age: "y", family: "z", lastYearFamily: "w"},
    {cat: "x", age: "y", model: "z", lastYearModel: "w"}
]

x.map(e => {
    if (isFamily(e)) {
        return e.family;
    } else {
        return e.model;
    }
})

Upvotes: 1

GBF_Gabriel
GBF_Gabriel

Reputation: 2656

You aren't hinting to the compiler that the string 'family' in the Props interface should infer to Family[] on the attributions. You are instead stating that attributionType has no correlation to attributions.

    // your code
    interface Props {
        attributionType: 'family' | 'model';
        attributions?: Family[] | Model[];
    }

To hint that you want this to the compiler, you can gather the types you need in different interfaces, and only then join them:

interface FamilyProp {
  attributionType: "family";
  attributions?: Family[];
}

interface ModelProp {
  attributionType: "model";
  attributions?: Model[];
}

type Props = ModelProp | FamilyProp;

function foo(bar: Props) {
    if (bar.attributionType === "model") {
        bar.attributions[0].lastYearModel; // compiles fine
    }
    if (bar.attributionType === "family") {
        bar.attributions[0].lastYearFamily; // compiles fine
    }
}

The compiler will infer the types inside the ifs thanks to Type Guard.

Upvotes: 1

Related Questions