Advena
Advena

Reputation: 2233

Using props.children and styled-component "as" prop with typescript

I've looked through several existing solutions and posts and couldn't really find any solution.

So, I'm using React with Typescript and styled-component.

One part of my project is the Heading component. Ideally, I imagined to used it like <Heading level={2}>Hello world!</Heading> wherever I need it.

Here is the simplified Codesandbox Code of it

However, the <S.Heading throws Errors in my Linter, even though it seems to work visually speaking.

Error

This JSX tag's 'children' prop expects type 'never' which requires multiple children, but only a single child was provided.ts(2745)

No overload matches this call. This JSX tag's 'children' prop expects type 'never' which requires multiple children, but only a single child was provided.ts(2769)

Not sure what's wrong about my code since I think I followed most recommendations..

Upvotes: 0

Views: 904

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42218

Typescript template literal types are not as smart as you might think. You might expect h${props.level} to evaluate to a union type "h1" | "h2" | ... based on the type of your props.level variable. Instead it is just string. So you will need an as assertion somewhere in your code in order to declare that this string is a valid key of JSX.IntrinsicElements.

Getting the union "h1" | "h2" | ... as a type is tough because Props['level'] is a union of number literals rather than string literals. But we don't really need the union because t doesn't really matter which heading type it is. You can use as "h1" and you'll be fine.

export default function Heading(props: Props) {
  return <S.Heading as={`h${props.level}` as "h1"}>{props.children}</S.Heading>;
}

Out of curiosity I wanted to see if I could extract the valid h* tags from JSX.IntrinsicElements. I came up with a solution that almost works based on string length, but I forgot about hr!

type HTag = {
    [K in keyof JSX.IntrinsicElements]: K extends `h${string}${infer B}` ? B extends "" ? K : never : never
}[keyof JSX.IntrinsicElements]

evaluates to:

"h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "hr"

Upvotes: 1

Related Questions