Definitelytyped: [@types/styled-components] How do I specify props for a different tag when using `as` in attrs?

Created on 1 May 2019  路  2Comments  路  Source: DefinitelyTyped/DefinitelyTyped

If you know how to fix the issue, make a pull request instead.

  • [x] I tried using the @types/xxxx package and had problems.
  • [x] I tried using the latest stable version of tsc. https://www.npmjs.com/package/typescript
  • [ ] I have a question that is inappropriate for StackOverflow. (Please ask any appropriate questions there).
  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @Igorbek @Igmat @Lavoaster @Jessidhia

I initially asked on Stack Overflow, but posting here with the hopes I can get a little more traction.

Summary

Given:

const Link = styled.a`
  border: solid 1px black;
  border-radius: 5px;
  padding: 5px;
  margin: 10px 5px;
`;

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
const LinkAsButton = styled(Link).attrs<ButtonProps>
>(({
  as: 'button',
  className: 'btn btn-primary'
})`
  border: solid 1px red;
`;

How do I pass button specific props (e.g. disabled) to LinkAsButton?

Repo (includes branch for v3 and v4) demonstrating issue: https://github.com/arteforme/v3-v4-styled-components-migration

Version 3

In v3, I'm able to do the following:

package.json

"dependencies": {
    "@types/react": "^16.8.8",
    "@types/react-dom": "^16.8.2",
    "react": "^16.8.4",
    "react-dom": "^16.8.4",
    "react-scripts": "2.1.8",
    "styled-components": "^3.3.3",
    "typescript": "^3.3.3333"
  }

Component definition

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;

const Link = styled.a`
  border: solid 1px black;
  border-radius: 5px;
  padding: 5px;
  margin: 10px 5px;
`;

const LinkAsButton = Link.withComponent('button')
  .extend.attrs<ButtonProps>({
  className: 'btn btn-primary'
})`
  border: solid 1px red;
`;

and in consuming code, I can specify button specific props (e.g. disabled, form, etc) on the LinkAsButton component.

Version 4

In v4, I'd like to achieve the same; however, I've not been successful in determining how to go about doing so. Per the latest documentation, I've updated LinkAsButton to no longer call extend or withComponent and instead wrap Link in a call to styled and uses as to specify the tag. The v4 version looks like:

package.json

dependencies": {
    "@types/react": "^16.8.14",
    "@types/styled-components": "^4.1.14",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "2.1.8",
    "styled-components": "^4.2.0",
    "typescript": "^3.4.5"
  }

Component definition

const LinkAsButton = styled(Link).attrs<ButtonProps>
>(({
  as: 'button',
  className: 'btn btn-primary'
})`
  border: solid 1px red;
`;

and in consuming code, I specify disabled={true} on a LinkAsButton instance, but this does not compile. Instead, the following error results:

_Property 'disabled' does not exist on type 'IntrinsicAttributes & Pick, HTMLAnchorElement>, "type" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | ... 257 more ... | "referrerPolicy"> & { ...; }, "type" | ... 261 more ... | "referrerPolicy"> & Partial<...>, "type" | ....'. TS2322_

Reviewing attrs definition

Taking a look at the definition file, I notice that two generic arguments can be specified.

attrs <
  U,
  NewA extends Partial<StyledComponentPropsWithRef<C> & U> & {
    [others: string]: any;
  } = {}
    > (
      attrs: Attrs<StyledComponentPropsWithRef<C> & U, NewA, T>
    ): ThemedStyledFunction<C, T, O & NewA, A | keyof NewA>;

From what I can gather, C is the wrapped component and I believe NewA is NewAttributes. I'm not able to determine what U is in this case, but I try specifying the second generic parameter.

const LinkAsButton = styled(AnchorLink).attrs<{}, ButtonProps>`
  // omitted for brevity
`

And in doing so, that results in the following error:

_Type 'ButtonHTMLAttributes' is not assignable to type 'Partial, HTMLAnchorElement>, "type" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | ... 257 more ... | "referrerPolicy"> & { ...; } & ButtonHTMLAttributes<...>>'.
Types of property 'onCopy' are incompatible._

With that information in hand, I try:

const LinkAsButton = styled(Link).attrs<
  {},
  { as: string; className: string; disabled?: boolean }
>({
  as: 'button',
  className: 'btn btn-primary'
})``;

Which works as I can now specify disabled in the markup, but not any other button specific props (e.g. form)

Non ideal solution

One approach would be to do the following:

const LinkAsButton = styled(Link)<ButtonProps>`
  border: solid 1px red;
`;

Consuming code:

<div className="App">    
    <LinkAsButton as="button" className="btn-btn-primary" disabled={true}>
        First button
    </LinkAsButton>
    <LinkAsButton as="button" className="btn-btn-primary" disabled={true}>
        Second button
    </LinkAsButton>
</div>

However, this would lead to a lot of code duplication :(.

Most helpful comment

FYI Emotion decided not to implement attrs as they considered this feature as not required.
You could just do what you are doing now using defaultProps:

const LinkAsButton = styled(Link)<ButtonProps>`
  border: solid 1px red;
`;

LinkAsButton.defaultProps = {
  as: 'button',
  className: 'btn btn-primary'
};

It's actually simpler for new comers and more readable.

All 2 comments

Got it working by moving the generic param after the call to .attrs.

const LinkAsButton = styled(Link).attrs
>(({
  as: 'button',
  className: 'btn btn-primary'
})<ButtonProps>`
  border: solid 1px red;
`;

Now any prop defined in ButtonProps can be set in the JSX

FYI Emotion decided not to implement attrs as they considered this feature as not required.
You could just do what you are doing now using defaultProps:

const LinkAsButton = styled(Link)<ButtonProps>`
  border: solid 1px red;
`;

LinkAsButton.defaultProps = {
  as: 'button',
  className: 'btn btn-primary'
};

It's actually simpler for new comers and more readable.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fasatrix picture fasatrix  路  3Comments

jrmcdona picture jrmcdona  路  3Comments

tyv picture tyv  路  3Comments

JWT
svipas picture svipas  路  3Comments

demisx picture demisx  路  3Comments