emotion-js / emotion

👩‍🎤 CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.47k stars 1.11k forks source link

Destructuring props on a styled component throws a type error on property "as" #2448

Open ni-hfriese opened 3 years ago

ni-hfriese commented 3 years ago

Current behaviour: Receive the following error when destructuring props onto an emotion/styled component. Type '{ children: (ReactNode | Element)[]; accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; ... 356 more ...; key?: Key | ... 1 more ... | undefined; }' is not assignable to type '{ theme?: Theme | undefined; as?: ElementType<any> | undefined; }'. Types of property 'as' are incompatible. Type 'string | undefined' is not assignable to type 'ElementType<any> | undefined'. Type 'string' is not assignable to type 'ElementType<any> | undefined'. This occurs on any tag you destruct functional component props on. styled.'s "as" property doesn't correspond with React.HTMLProps's "as" property (can also be HTMLButtonElement).

To reproduce: See Example Here.

  1. Create a new functional component.
  2. Within functional component, add an emotion styled component.
  3. Add some arbitrary properties (for example elementHeight, doesn't really matter) to the React.FC Props interface.
  4. Make sure to extend the React element of whatever HTML element you wish to destruct default properties on.
  5. destruct props ({...props}) on the intended HTML element

Expected behaviour:

Environment information:

Andarist commented 3 years ago

I recommend using React.HTMLAttributes<HTMLElement> over React.HTMLProps<HTMLElement> because this is a type of props for <nav/> element. I'm not exactly sure in what context HTMLProps should be used.

ni-hfriese commented 3 years ago

That resolved my specific issue, but I'm still curious if those two as properties should be aligned to prevent issues in the future?

robinmetral commented 3 years ago

It also seems that HTMLProps<HTMLInputElement> includes extra props (in contrast to HTMLAttributes<HTMLInputElement>), like the disabled and required props.

So switching to HTMLProps worked for me in some components, but not all—I can also Omit<HTMLProps<HTMLInputElement>, "as"> but it's not super nice.

Did you end up solving this differently?


Edit:

Hm so I found this and it looks like the way to go would be InputHTMLAttributes<HTMLInputElement>. Works well on my end and I could clean up some old Omit<> 🎉

The only thing I'm not sure of is why some elements are missing their *HTMLAttributes types, like ul. I assume that it's because they don't have any other props beyond the HTMLAttribute type (all other element-specific generics extend it), so for these I'd just use it directly: HTMLAttributes<HTMLUListElement>.

okerx commented 2 years ago

@Andarist Thanks for your recommendation! however, HTMLAttributes<HTMLElement> doesn't really solve the issue, it just omits the property as, and therefore the error will disappear. but, what if I wanted to use the prop as? I can't! because it doesn't exist on the type we inherited from React.HTMLAttributes<HTMLElement>.

as @robinmetral mentioned, I could solve the issue by using a specific type for each element (e.g. InputHTMLAttributes<HTMLInputElement>). However, not every HTML element has a specific type, for instance, the div element doesn't. I tried something like BaseHTMLAttributes and it still omits the as prop.

I suggest adding string to the StyledComponent's as property so that it would be sth like as?: React.ElementType | string If we can do that, we will be able to use HTMLProps<T> again without the need to omit as.

Edit

A more interesting solution would be if there's a way we can inherit the type StyledComponent instead, and pass the HTML element type to get the proper attributes, for instance, StyledComponent<HTMLDivElement>. I'm not sure if this is feasible though.

vincerubinetti commented 2 years ago

Just wanted to share my experience for anyone looking for the same issue.

I had a simple button component like this:

interface Props {
  text?: string;
  icon?: string;
}

const Button = ({
  text,
  icon,
  ...props
}: Props & HTMLProps<HTMLButtonElement>) => {
  return (
    <button {...props}>
      {text && <span>{text}</span>}
      {icon && <Icon icon={icon} />}
    </button>
  );
};

export default Button;

Wasn't even including an emotion css prop yet, but was getting the following (slightly different) typescript error:

Type '{ children: ("" | Element | undefined)[]; accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; ... 356 more ...; css: SerializedStyles; }' is not assignable to type 'ClassAttributes<HTMLButtonElement> & ButtonHTMLAttributes<HTMLButtonElement> & { css?: Interpolation<Theme>; }'.
  Type '{ children: ("" | Element | undefined)[]; accept?: string | undefined; acceptCharset?: string | undefined; action?: string | undefined; allowFullScreen?: boolean | undefined; ... 356 more ...; css: SerializedStyles; }' is not assignable to type 'ButtonHTMLAttributes<HTMLButtonElement>'.
    Types of property 'type' are incompatible.
      Type 'string | undefined' is not assignable to type '"button" | "submit" | "reset" | undefined'.
        Type 'string' is not assignable to type '"button" | "submit" | "reset" | undefined'.ts(2322)

Changing from HTMLProps to HTMLAttributes made the error go away. This stackoverflow post says that HTMLProps has more stuff like ref, though more information would be good.

Maybe you're supposed to use HTMLAttributes when referring to a native dom element, and HTMLProps when referring to a custom component that wraps a native dom element?