skip to Main Content

I’m trying to create an array of objects that can be passed into a react component. I’d like to require each potential object (within the array argument) passed in to match one of the potential types provided. So far, the union is allowing any fields provided in any of the various interfaces to be valid in other interfaces, when in reality I’d prefer it be more restrictive. I’ve been going through the documentation scanning over unions and control flow. I’m not sure if this can be done, but it seems like … perhaps it should be possible to accomplish in a dynamic way without having to be explicit?

interface IAnimal {
  eat: () => void,
  name: string
}

interface IDog extends IAnimal {
 bark: () => void,
}

interface ICat extends IAnimal {
 meow: () => void,
}

type ValidAnimals = IDog | ICat
// it needs to be either only a dog, or only a cat.

<MyComponent pets=[
 {
   name: "Sparky",
   eat: () => {},
   bark: () => {}
 },
 {
   name: "Whiskers",
   eat: () => {},
   meow: () => {}
 },
 {
   name: "Kitty",
   eat: () => {},
   bark: () => {}  // this should be not allowed, does not exist on cats (casting doesn't resolve this)
   meow: () => {}
 },
]
/>

Is there a means to have typescript identify that the provided cat in this case does not exactly match any of the possible types (ICat / IDog) ?

2

Answers


  1. Assigning a union type to your pets will infer that it is a dog or cat.

    • If you want an array of Cats or Dogs, you would explicitly set the type to Idog[] or Icat[]
    • If you want an array with Cats or Dogs, use the union type const pets: IDog | ICat
    • Assigning a type by inferring the first element of the array is an anti-pattern. You’d like to explicitly state the variable’s valid options, not vice versa.

    Yet, the line that will fail will be the second property on the "Kitty" object.

    type ValidAnimals = IDog | ICat
    
    const pets: ValidAnimals[] = [
      {
        name: "Sparky",
        eat: () => { },
        bark: () => { }
      },
      {
        name: "Whiskers",
        eat: () => { },
        meow: () => { }
      },
      {
        name: "Kitty",
        eat: () => { },
        bark: () => { }
        meow: () => { } // fails here
      },
    ]
    
    Login or Signup to reply.
  2. sure-

    interface IAnimal {
        eat: () => void;
        name: string;
    }
    
    interface IDog extends IAnimal {
        bark: () => void;
    }
    
    interface ICat extends IAnimal {
        meow: () => void;
    }
    
    function MyComponent({ pets }: { pets: (IDog | ICat)[] }) {
        // do something with pets
    
        return null;
    }
    
    function AnotherComponent() {
        const pets = [
            {
                bark: () => {},
                eat: () => {},
                name: 'Sparky',
            },
            {
                eat: () => {},
                meow: () => {},
                name: 'Whiskers',
            },
            {
                // TS2353: Object literal may only specify known properties, and 'bark' does not exist in type 'ICat'.
                // now you get a type error because of satisfies
                bark: () => {}, 
                eat: () => {},
                meow: () => {},
                name: 'Kitty',
            } satisfies ICat,
        ] satisfies (IDog | ICat)[];
    
        return <MyComponent pets={pets} />;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search