Home > Software design >  Typescript require one of props and omit others
Typescript require one of props and omit others

Time:12-09

I have a type Identifiers which hold different props and will be used as React component property.

type Identifiers = {
  alias: string;
  personalId: number;
  customerId: number;
};

I want to create a type which will allow to specify only one of identifiers and omit others (not make others optional).

The idea is that specifying any prop from identifiers if one of props already passed to react component should give an error and there should be no suggested other props (if possible). Is there are elegant way to to create type for that?

P.S. I hope this is not a duplicate, but I was unable to find exactly what I want as other solutions require union from many types where other props set to never instead of omitting them or just made optional.

CodePudding user response:

To create a utility type that takes in a type with multiple properties and produces an union of types where each individual member corresponds to one of the keys of the original multi-property type, you could do something like this:

type Split<T> = { [K in keyof T]: Pick<T, K> }[keyof T]

This will mean the following:

type Foo = {
  a: A,
  b: B,
  c: C
}

type Bar = Split<Foo>
//   ~~~
//   Bar = Pick<Foo, "a"> | Pick<Foo, "b"> | Pick<Foo, "c">
//       = { a: A }       | { b: B }       | { c: C }

Now, I understand you want IntelliSense to not suggest the other properties, like it does here:

type Foo = { a: number, b: string }
const foo: Split<Foo> = { a: 0, … }
//                              ^
//                              |   (property) b: string

But I'm not aware of a way to make TypeScript totally ignore the existence of b: String, since { a: number, b: string } extends { a: number } | { b: string }; as long as the shape of a value satisfies the type assigned to it, TypeScript will allow it. The TypeScript language service in your editor is "smart enough" to know to suggest the possible properties to you — it's not directly related to the type system itself and you cannot influence it in this case.


Since you wanted to have the other properties set to never in the union, you could do something like:

type Split<T> = {
  [K in keyof T]:
    Pick<T, K> &
    Partial<
      Record<
        Exclude<keyof T, K>,
        never
      >
    >;
}[keyof T]

Now you'll have:

type Foo = {
  a: A,
  b: B,
  c: C
}

type Bar = Split<Foo>
//   ~~~
//   Bar = (Pick<Foo, "a"> & Partial<Record<"b" | "c", never>>)
//       | (Pick<Foo, "b"> & Partial<Record<"a" | "c", never>>)
//       | (Pick<Foo, "c"> & Partial<Record<"a" | "b", never>>)
//       = ({ a: A }       & { b?: never, c?: never })
//       | ({ b: B }       & { a?: never, c?: never })
//       | ({ c: C }       & { a?: never, b?: never })
//       = ({ a: A, b?: never, c?: never })
//       | ({ b: B, a?: never, c?: never })
//       | ({ c: C, a?: never, b?: never })

At least with the current TSC version, the errors this approach produces seem to be misleading and generally hard to read anyhow. I found another post with this problem addressed by Jcalz; they have a more elegant solution: https://stackoverflow.com/a/57576688/11308378

CodePudding user response:

To create a type that allows only one of the properties from the Identifiers type to be specified, you can use the Pick type from TypeScript. Pick takes a type and one or more keys from that type, and creates a new type that includes only the specified keys.

For example, if you want to create a type that allows only the alias property from Identifiers to be specified, you can use Pick like this:

type AliasIdentifier = Pick<Identifiers, "alias">;

You can then use this new AliasIdentifier type wherever you need to specify only the alias property from Identifiers. For example, you could use it as the type for a React component prop like this:

function MyComponent(props: { identifier: AliasIdentifier }) {
  // ...
}

Now, if you try to pass an Identifiers object to the identifier prop of MyComponent, TypeScript will give an error, because MyComponent expects an object with only the alias property, not the full Identifiers object.

  • Related