Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutually exclusive group #138

Open
akshgpt7 opened this issue Apr 24, 2020 · 1 comment
Open

Mutually exclusive group #138

akshgpt7 opened this issue Apr 24, 2020 · 1 comment

Comments

@akshgpt7
Copy link

akshgpt7 commented Apr 24, 2020

Is your feature request related to a real problem or use-case?

It'd be a good idea to be able to create mutually exclusive groups for options that cannot coexist.

Use cases:

Many APIs have filters of which exactly one can be used at a time. So to define their schemas, such a feature can prove to be really helpful.
Also, there are many options/flags which cannot be used simultaneously in one command line.

Describe a solution including usage in code example

Possible code example:
Say, we have 2 properties declared in a mutually exclusive group in an interface. Now, exactly one of those properties can exist, with no two properties coexisting simultaneously in an object having type of that interface.

interface args {
  (arg1: string | arg2: string);
}

Additionally, we can also think of making the entire group optional, maybe with something like this:

interface args {
  ?(arg1: string | arg2: string);
}

Example:

interface args {
  (arg1: string | arg2: string);
}

Now, if this interface is used as a function parameter:

function command(params:args) {
  //do something
}

let obj: args = {arg1: "foo", arg2: "bar"};
command(obj);

The following code snippet should throw an error, since the two properties arg1 and arg2 cannot coexist in the same object.

Who does this impact? Who is this for?

Many people using TypeScript must have felt the need for declaring mutually exclusive groups.

@malyzeli
Copy link

This would be really nice to have it included in utility-types library!

It is possible to define such shape using discriminated union type, but it requires having one shared property to infer correct type. That is acceptable approach for many cases, where some properties would be shared anyway.

interface TextInput {
  type: "text";
  value: string;
}

interface NumberInput {
  type: "number";
  value: number;
}

function Input(props: TextInput | NumberInput) {}

But then there are cases where some properties should be mutually exclusive, without the necessity to use additional "type" property for proper type inference.

interface ImageFile {
  /** Local file URL is resolved internally. */
  file: File;
}

interface ImageSrc {
  src: string;
}

function Image(props: ImageFile | ImageSrc) {}

We want to say that Image can have either file or src prop, but actually the following code is still valid, because TypeScript does not treat unions as exclusive by default.

Image({ file: foo, src: "bar" }); // no error

The solution is to extend our interfaces to also describe properties which should not be present, defining them as optional value of undefined (or never) type.

interface ImageFileX {
  /** Local file URL is resolved internally. */
  file: File;
  src?: undefined;
}

interface ImageSrcX {
  src: string;
  file?: undefined;
}

function ImageX(props: ImageFileX | ImageSrcX) {}

Now it works as expected.

ImageX({ file: foo }); // ok
ImageX({ src: "bar" }); // ok
ImageX({ file: foo, src: "bar" }); // error

But there is a lot of boilerplate which does not really provide any additional information and I think it actually makes the code less readable, especially when you have multiple related properties bound together...

interface BaseInput<T> {
  name?: string;
  value: T;
  error: boolean;
  onChange: (next: T) => void;
}

interface ManagedInput<T> {
  name: string;
  value?: never;
  error?: never;
  onChange?: never;
}

There seems to be quite long discussion in related issue presenting some solutions, so it might be worth exploring...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants