15

I'm writing a React app using TypeScript. I use material-ui for my components. I'm writing a custom wrapper for material-ui's Button component. It looks like this:

import MUIButton, { ButtonProps } from "@material-ui/core/Button";
import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
import classNames from "classnames";
import React, { PureComponent } from "react";
import styles from "./styles";

export interface OwnProps {
  color: "primary" | "danger" | "warning" | "transparent";
  size: "sm" | "lg";
}

export interface Props
  extends WithStyles<typeof styles>,
    OwnProps,
    ButtonProps {}

export class Button extends PureComponent<Props> {
  render() {
    const { color, size, classes, children, ...rest } = this.props;
    const btnClasses = classNames({
      [classes.button]: true,
      [classes[size]]: size,
      [classes[color]]: color
    });
    return (
      <MUIButton {...rest} className={btnClasses}>
        {children}
      </MUIButton>
    );
  }
}

export default withStyles(styles)(Button);

The problem is that here that the definition of Props throw the error message:

Named property 'color' of types 'OwnProps' and 'ButtonProps' are not identical.
[ts]
Interface 'Props' cannot simultaneously extend types 'OwnProps' and 'ButtonProps'.
  Named property 'size' of types 'OwnProps' and 'ButtonProps' are not identical.
Named property 'color' of types 'OwnProps' and 'ButtonProps' are not identical.
[ts]
Interface 'Props' cannot simultaneously extend types 'OwnProps' and 'ButtonProps'.
  Named property 'size' of types 'OwnProps' and 'ButtonProps' are not identical.

This error goes away if I instead write:

export class Button extends PureComponent<Props & ButtonProps> {

But then when using the Button the props color and size throw the error:

The expected type comes from property 'color' which is declared here on type 'IntrinsicAttributes & Pick<Props & ButtonProps, ...

How can I correctly tell the component that it has the the Props I defined (OwnProps) as well as the props that come from the Button as usual?

J. Hesters
  • 13,117
  • 31
  • 133
  • 249

1 Answers1

20

Use TypeScript's Omit type to exclude specific properties from another type:

Constructs a type by picking all properties from Type and then removing Keys (string literal or union of string literals).

import { ButtonProps } from "@material-ui/core/Button";

export type OwnProps = Omit<ButtonProps, "color" | "size"> & {
  color: "primary" | "danger" | "warning" | "transparent";
  size: "sm" | "lg";
}

class MyButton extends React.Component<OwnProps> {
}
Mac
  • 1,632
  • 9
  • 15
Daniel Khoroshko
  • 2,623
  • 15
  • 26
  • I hope it helps – Daniel Khoroshko Nov 04 '18 at 10:13
  • This works as far as the OwnProps warning goes, but now for `export interface Props extends WithStyles, OwnProps {}` I get the error: and 'OwnProps' are not identical.` Similar to the second one I described in my question. Do you have any idea how to solve this? – J. Hesters Nov 04 '18 at 15:41
  • `Interface 'Props' cannot simultaneously extend types '{ classes: Record<"primary" | "danger" | "warning" | "transparent" | "sm" | "lg" | "button", string>; innerRef?: string | ((instance: any) => any) | RefObject | undefined; }' and 'OwnProps'. Named property 'classes' of types '{ classes: Record<"primary" | "danger" | "warning" | "transparent" | "sm" | "lg" | "button", string>; innerRef?: string | ((instance: any) => any) | RefObject | undefined; }` – J. Hesters Nov 04 '18 at 15:43
  • 1
    Yes, this happens because mui button expects its own classes, so you could exclude "classes" in Omit as well, it should work then – Daniel Khoroshko Nov 04 '18 at 21:48
  • It did! Thank you :) – J. Hesters Nov 05 '18 at 08:10
  • 15
    This answer doesn't actually explain how to solve the problem. The solution is very specific to this question, which is ok, but adding an explanation will allow it to provide value to others with similar but unique circumstances surrounding their question. – TetraDev Nov 02 '20 at 21:46