3

I need to figure out the TypeScript type of data coming into my React component. The data could represent one of two things, cats or dogs:

my-component.tsx:

export const MyComponent = { props: {
  data: any; // I wish I could use the union type "Cat[] | Dog[]" here
}}: JSX.Element => {
  ...

  return (
    <ComponentNotModifiableByMe
      data={props.data}
      // If "Cat[] | Dog[]" is used above, TS says "Type 'Dog[]' is not assignable to type 'Cat[]'. ts(2322)"
      // If "any" is used above, this works okay, but defeats the purpose of TypeScript
      ...
    />
  );
}

cat-stuff.tsx:

export type Cat = {
  foo: string;
  bar: string;
  address: string;
  birthday: string;
}

dog-stuff.tsx:

export type Dog = {
  bar: string;
  address: string;
  birthday: string;
}

There appear to be five ways to check what things are in TypeScript, based on an existing SO answer:

  1. instanceof

    N/A, this checks against classes, but I have types

  2. typeof

    N/A, this checks against primitive types, but I have custom TypeScript types

  3. in

    This would probably work if I had Cat | Dog. But I have arrays, and it's valid in my app for data to be an empty array. So, I can't rely on checking the first element of data, because it might not exist.

  4. user-defined type guard, AKA type predicates

    This seems the most promising, but as with #3, there might not be any actual data for me to check, so I don't know what I would put in an isCat() or isDog() function.

  5. discriminated union

    N/A, there is no kind property or equivalent on my types that is a unique identifier; adding one could be a workaround, but it might not work anyways, for the same reason as #3 and #4 above.

How can I figure out the type of data in a way that works for empty arrays? Currently I'm manually passing an extra prop to MyComponent but that doesn't feel like good coding practice. If there's a React way around this problem (instead of a TypeScript way), that'd be fine too.

8
  • How about Cat[] | Dog[] Commented Nov 22, 2021 at 19:26
  • From where should the type be inferred if the array is empty? The extra prop seems unavoidable. Commented Nov 22, 2021 at 19:30
  • Why can't you simply type data as an union type? Either define it as Cat[] | Dog[] or (Cat | Dog)[]. Commented Nov 22, 2021 at 19:36
  • ComponentNotModifiableByMe only seems to accept cats. Do you have a conditional in your real code where you use another component if you have a dog? Commented Nov 22, 2021 at 19:48
  • @ShamPooSham fair question but there's no conditional. I can confirm ComponentNotModifiableByMe works with both Cats and Dogs. It's just a generic list display widget. I'm guessing the TS compiler always chooses to say "[the second of the union types] is not assignable to [the first of the union types]" in this situation where it can't determine the type for itself. If I reverse the order, the error is reversed as well. Commented Nov 22, 2021 at 20:22

1 Answer 1

1

If the array is empty there is no possible way to infer its type, so your extra prop is required.

You could write something like this:

export const MyComponent = { props: {
  data: Cat[] | Dog[],
  cats: boolean,
  dogs: boolean,
}}: JSX.Element => {

  // After calling this function in a if, data will be typed as Cat[]
  isArrayOfCats(data: Cat[] | Dog[]): data is Cat[] {
    return cats;
  }

  // After calling this function in a if, data will be typed as Dog[]
  isArrayOfDogs(data: Cat[] | Dog[]): data is Dog[] {
    return dogs;
  }

  return (
    <ComponentNotModifiableByMe
      data={props.data}
      // If "Cat[] | Dog[]" is used above, TS says "Type 'Dog[]' is not assignable to type 'Cat[]'. ts(2322)"
      // If "any" is used above, this works okay, but defeats the purpose of TypeScript
      ...
    />
  );
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.