Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending an MUI component gives TS error about "css" property missing

We have extended SnackbarContent component with our custom one (MySnackbarContent):

export interface MySnackbarContentProps extends Omit<SnackbarContentProps, 'variant'> {
  variant?: MyCustomVariant;
  type?: MyCustomType;
  banner?: boolean;
  // ...
}

const MySnackbarContent = forwardRef<HTMLElement, MySnackbarContentProps>(props: MySnackbarContentProps, ref) => {
  const { variant = 'normal', type = 'default', banner = false, ...other } = props;
  const className = ...;

  return <SnackbarContent ref={ref} className={className} {...other} />
}

Here is the error that I get when I use the custom SnackbarContent component:

Property 'css' is missing in type '{ type: "error" | "default" | "success" | undefined; message: string; action: Element; }' but required in type 'Pick<MySnackbarContentProps, "hidden" | "style" | "onSelect" | "slot" | "title" | "className" | "classes" | "innerRef" | "defaultChecked" | "defaultValue" | ... 258 more ... | "actionClickHandler">'.

I do not understand why css property is a problem here because we are not using emotion or styled-component (we are using JSS).

Weirdly, everything types are properly checked when I remove forwardRef.

A possible way to mitigate this issue is to add css property in MySnackbarContentProps and set it to something like: css?: null. For some reason, css property is a required property. Not 100% sure why that is the case here. What am I missing here?

  • MUI Version: 4.9.10
  • TS Version: 3.6.2
  • React: 16.13.0
  • React Types: 16.9.34

EDIT: Explanation for the root cause

There are three parts at play when trying to understand the root cause.

Firstly, Typescript allows augmenting any type and the result of the augmentation will be merging of properties in different definitions. For example, the code below:

interface Person {
  name: string;
}

interface Person {
  age: number;
}

will be the same as writing the following:

interface Person {
  name: string;
  age: number
}

Playground

This augmentation does not need to be in the same module/file; so, any module in the project (including node_modules) can augment any type in another module).

Secondly, Material UI allows passing all DOM properties to many of its components. So, it is essentially extending base React.DOMAttributes<T> (e.g Paper extends React.DOMAttributes<HTMLDivElement>) in a lot of components.

For me, I was able to find the problem because I knew that css is a property of emotion, which is used by Storybook; so, I dug deeper to see what emotion does and found the following: https://github.com/emotion-js/emotion/blob/31e610f2385d5a3dfd532b31f743e5f6b9fee43b/packages/react/types/index.d.ts#L99

So, emotion is augmenting React.DOMAttributes<T> by adding optional css property to it. If any type extends from this type, they will have access to css property. This was enough for me to identify the culprit. The library was exporting a utility that was meant to be used in Storybook (some storybook functions were being imported from those utilities). Removing that component from the output solved the issue. If you are using Storybook, check if the outputted package used any of the Storybook components.

One thing that I did not investigate was why the css was a mandatory property even though emotion was passing the prop as optional.

like image 734
Gasim Avatar asked Apr 30 '20 08:04

Gasim


1 Answers

Thanks for the follow-up, Gasim !

For me the root issue was also caused by Storybook being bundled with the application, because we're using Storybook files with a *.stories.tsx extension. Typescript loads all .tsx files in our setup, which in turn loads the global Emotion prop augment OP mentioned.

I had to add "exclude": ["**/*.stories.tsx"] to our tsconfig.json to fix it.

like image 107
tichopad Avatar answered Nov 01 '22 22:11

tichopad