Search code examples
javascriptreactjstypescriptstyled-components

Using 'as' polymorphic prop of styled-components with typescript


I was trying to implement a Typography react component.
As you can see below, I got variant as an input prop and used it as index of VariantsMap object to get corresponding html tag name.

Then I used styled-components 'as' polymorphic prop to render it as selected html tag.

but I keep get this error :
No overload matches this call. Overload 1 of 2, '(props: Omit<Omit<Pick<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "key" | keyof HTMLAttributes<...>> & { ...; } & { ...; }, never> & Partial<...>, "theme"> & { ...; } & { ...; }): ReactElement<...>', gave the following error. Type 'string' is not assignable to type 'undefined'.

I found in @types/styled-component that 'as' props can be 'never | undefined', and my variantsMap returns string type.
But I really want to use this 'as' prop with my variant-specific html tag selection feature.

Is there any way to solve this problem?

const variantMap = {
  h1: 'h1',
  h2: 'h2',
  h3: 'h3',
  h4: 'h4',
  h5: 'h5',
  h6: 'h6',
  subheading1: 'h6',
  subheading2: 'h6',
  body1: 'p',
  body2: 'p',
};

export const Typography = ({ variant : string }) => { 

      const selectedComponent = variantMap[variant];

      return (<TypographyRoot
        as={selectedComponent}
        variant={variant}
        {...props}
      >
        {children}
      </TypographyRoot>);
}

Solution

  • First of all, export const Typography = ({ variant : string }) => {} is invalid syntax.

    You just changed the name of destructured variant to string. You did not provide a type.

    The reason you have an error even with valid string type like here export const Typography = ({ variant }:{variant: string}) => {} is that variantMap expects as a key h1 | 'h2' |'h3' ... keys whereas string is much wider. I'd willing to bet that you don't want to assign foo string to variant property.

    IN order to fix it, you just need to make variantMap immutable and apply appropriate constraint to variantMap:

    import React from 'react'
    import styled from "styled-components";
    
    const Div = styled.div`
      color: red;
    `;
    
    const VariantMap = {
      h1: 'h1',
      h2: 'h2',
      h3: 'h3',
      h4: 'h4',
      h5: 'h5',
      h6: 'h6',
      subheading1: 'h6',
      subheading2: 'h6',
      body1: 'p',
      body2: 'p',
    } as const;
    
    type Props = {
      variant: keyof typeof VariantMap
    }
    export const Typography = ({ variant }: Props) => {
    
      const selectedComponent = VariantMap[variant];
    
      return <Div
        as={selectedComponent}
      />
    }
    

    Now styled-component is happy.

    Playground


    Making the map immutable saved me. Can you explain why not making the map immutable makes Typescript complain?

    Without as const all values of VariantMap are infered as a string instead of literals "h1", "h2". Which in turn affects selectedComponent, since this const represents a value of VariantMap. I mean selectedComponent becomes just a regular string instead of string literal, whereas as property expects strict string literal h1, h2, h3, ...