56

In TypeScript, you can combine two interface types like this

interface Foo {
    var1: string
}

interface Bar {
    var2: string
}

type Combined = Foo & Bar

Instead of combining keys, I want to exclude keys from one interface to another. Is there anyway you can do it in TypeScript?

The reason is, I have an HOC, which manages a property value for other wrapped component like this

export default function valueHOC<P> (
  Comp: React.ComponentClass<P> | React.StatelessComponent<P>
): React.ComponentClass<P> {
  return class WrappedComponent extends React.Component<P, State> {
    render () {
      return (
        <Comp
          {...this.props}
          value={this.state.value}
        />
      )
    }
}

With that I can write

const ValuedComponent = valueHOC(MyComponent)

then

<ValuedComponent />

but the problem is, the returned component type is also using the props type from given component, so TypeScript will complain and ask me to provide the value prop. As a result, I will have to write something like

<ValuedComponent value="foo" />

Which the value will not be used anyway. What I want here is to return an interface without specific keys, I want to have something like this

React.ComponentClass<P - {value: string}>

Then the value will not be needed in the returned component. Is it possible in TypeScript for now?

Fang-Pen Lin
  • 13,420
  • 15
  • 66
  • 96

5 Answers5

79

In TypeScript 2.8 you can now do the following:

interface Foo {
    attribute1: string;
    optional2?: string;
    excludePlease: string;
}

// Typescript 3.5+ defines Omit for you.
// In earlier Typescript versions define Omit:
// type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>

// Use Omit to exclude one or more fields (use "excludePlease"|"field2"|"field3" etc to exclude multiple)
type Bar = Omit<Foo, "excludePlease">
const b: Bar = {
    attribute1: ''
};

So in relation to your question the following might be what you want:

export default function valueHOC<P> (
  Comp: React.ComponentClass<P> | React.StatelessComponent<P>
): React.ComponentClass<Omit<P, "value">> {
  return class WrappedComponent extends React.Component<Omit<P, "value">, State> {
    render () {
      return (
        <Comp
          {...this.props}
          value={this.state.value}
        />
      )
    }
}
AJP
  • 26,547
  • 23
  • 88
  • 127
  • 4
    Whilst this works, it will make optional parameters required. You can do this to prevent that: https://stackoverflow.com/a/48216010/616589 – Simon Aug 14 '18 at 17:07
  • 1
    I don't dispute the accuracy of the answer, but that's an absurdly round the houses way of achieving a simple end - it will take anyone reading it some non-trivial amount of time to work out just what the hell is going in. Is there any logic at all as to why the typescript team went this way? – havlock Sep 26 '18 at 17:49
  • @havlock sorry I recently edited it to simplify and improve the answer but I forget to also use the simplified `Omit` code in the simplify example. Is that better now? – AJP Sep 27 '18 at 13:37
  • 1
    @AJP I was moaning about typescript, not your answer, but thanks - yes, that is slightly more straightforward. Still absolutely bonkers that they make us jump through such ridiculous syntax to achieve something simple - I fear for the language if they really think this sort of thing is OK. – havlock Sep 28 '18 at 06:56
  • @havlock :) Would be interesting to see examples of other typed languages where this is dealt with better :) – AJP Sep 28 '18 at 16:41
  • This doesn't seem to work well when combining the props type back together. 'Pick> & { c: number; }' is not assignable to type 'OuterProps' http://www.typescriptlang.org/play/#code/JYOwLgpgTgZghgYwgAgBIHkDCAFKB7ABwGdkBvAKGWQQC5kQBXAWwCNoBucgX3PJgZAIwwPCGQALPAgA86BpCi5CJCAA9IIACYkMOfMQB8ACgL6idOQqXEAlGUrIwATwIoAkiBDRrJALzJ0JmAwWXlvMwAaZABrCCc8GDQsHwNOKigIMAYoMSNQL0UzOg8CnztfA3sqKgRRIjBHCHqaS3DlZH8KauqAOj78tuIIh27aZABGEeQuNOnuIA – zeroliu Sep 19 '19 at 01:06
  • apparently there is already a bug for this: https://github.com/Microsoft/TypeScript/issues/28938 – zeroliu Sep 19 '19 at 01:09
16
interface MyDialogProps extends Omit<DialogProps, 'onClose'> {
  id: string;
  onClose: (reason: string) => void;
}

export const MyDialog: React.FC<MyDialogProps> = ({ id, onClose, ...props) => (
  <Dialog onClose={() => onClose("whatever")} {...props} />
);
ilovett
  • 3,240
  • 33
  • 39
  • this should be the accepter answer, as well as Fabio's. The accepted answer creates a Type and not an Interface! – csotiriou Feb 24 '23 at 19:19
10

Example with several properties to "omit" using only interface

interface ABC {
    a: string;
    b: string;
    c: string;
}

type PropsABCOmitted = 'a' | 'c';
interface BD extends Omit<ABC, PropsABCOmitted> {
    d: string;
}

// instance example of interface BD:
const test: BD = {
    b: 'b',
    d: 'c'
};
7

There is utility-types library that has Subtract mapped type:

import { Subtract } from 'utility-types';

type Props = { name: string; age: number; visible: boolean };
type DefaultProps = { age: number };

type RequiredProps = Subtract<Props, DefaultProps>;
// Expect: { name: string; visible: boolean; }
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424
sad comrade
  • 1,341
  • 19
  • 21
-1

You can't remove properties from already existing interfaces. Even trying to extend existing interface with the interface having value property set as optional will return an error.

To avoid this issue, modify the typings of the target component so value property is optional.

e.g.

// ...
class MyComponent extends React.Component<{value?: string}, State> {
// ...
}

and then component produced when using High Order Function

const valuedComponent = valueHOC(MyComponent);

won't ask for value prop.

Michał Pietraszko
  • 5,666
  • 3
  • 21
  • 27