1

The point of this Type is to allow user to pass in either data, color, hasColor or data, info, hasInfo. Not any other combination.

type Props = {
  data: string;
} & (
  | {
      info?: string;
      hasInfo?: boolean;
      color?: never;
      hasColor?: never;
    }
  | {
      info?: never;
      hasInfo?: never;
      color?: string;
      hasColor?: boolean;
    }
);


function foo(props: Props) {
  console.log("bar");
}

foo({ data: "hello", info: "hello", hasInfo: true }); <----- TypeScript is happy
foo({ data: "hello", info: "hello", hasColor: true }); <----- TypeScript gives Error

Is there a cleaner way to implement this behavior using generics?

I tried this, but it looks like I'm messing up the logic of the ternary somehow:

type Info = { info: string; hasInfo: boolean };
type Color = { color: string; hasColor: boolean };
type Data = { data: string };

function foo<T>(
  props: keyof T extends keyof Info ? Data & Info : Data & Color
) {
  console.log("bar");
}

foo({ data: "hello", color: "hello", hasColor: true }); <----TypeScript gives Error

The first way works but looks so ugly.

6
  • Both examples seem to work as expected. What is the issue here? Commented Sep 25, 2022 at 14:50
  • @TobiasS. the second example is not correct. It gives this error: Argument of type '{ data: string; color: string; hasColor: boolean; }' is not assignable to parameter of type 'Data & Info'. Object literal may only specify known properties, and 'color' does not exist in type 'Data & Info'.ts(2345) Commented Sep 25, 2022 at 14:51
  • Here is a Playground of the first example. And here one of the second. They both have an error on the second foo call. The error is slightly different, but I still don't see what you are expecting. Commented Sep 25, 2022 at 15:11
  • The point of the first Type is to allow user to pass in the function either data, color, hasColor or data, info, hasInfo. Not any other combination. I'm trying to achieve dev.to/maissenayed/conditional-react-props-with-typescript-43lg but with generics. Commented Sep 25, 2022 at 15:19
  • @TobiasS. Here, I added a line to your second example Commented Sep 25, 2022 at 15:25

2 Answers 2

1

With your current definiton, TypeScript is not able to infer the type parameter T, so it is inferred as unknown when calling the function. Therefore, keyof T is just never and the conditional always resolves to the false branch.

To fix this, we can intersect the result of the conditional with T.

function foo<T>(
  props: keyof Info extends keyof T 
    ? Data & Info & T 
    : Data & Color & T
) {
  console.log("bar");
}

Also note that we have to switch keyof T extends keyof Info to keyof Info extends keyof T. keyof T will be the larger union and therefore can't extend keyof Info.


Playground

Sign up to request clarification or add additional context in comments.

Comments

1

After playing around I found another solid solution:

type Info = { info?: string; hasInfo?: boolean };
type Color = { color?: string; hasColor?: boolean };
type Data = { data: string };

function foo<T>(
  props: keyof Info extends keyof T
    ? Exclude<T, Color> & Data & Info
    : Exclude<T, Info> & Data & Color
) {
  console.log('bar');
}

Playground

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.