Search code examples
reactjstypescriptstyled-componentsemotion

Omit specific prop from an emotion styled component?


I have a BoxWithAs component, defined something like this:

const BoxWithAs = styled.div(
  {
    WebkitFontSmoothing: 'antialiased',
    MozOsxFontSmoothing: 'grayscale'
    // And more …
  }
);

All fine, but now I want to deprecate one of the default props coming from @emotion/styled, specifically the as prop.

I did this:

type BoxWithAsType = typeof BoxWithAs;
type BoxProps = Omit<React.ComponentProps<BoxWithAsType>, 'as'>;

/**
 * Component that does it all.
 */
const Box = (props: BoxProps) => {
  return <BoxWithAs {...props}>{props.children}</BoxWithAs>;
};

It does remove the prop ...

enter image description here

... but now the component itself looses all its StyledComponent typing information.

enter image description here

How can I structure this so I achieve both? My end goal is to deprecate the as prop, and rather encourage the usage of .withComponent (for Typescript reasons).


Solution

  • If we hover over BoxWithAs we see that it is of this type:

    StyledComponent<{
        theme?: Theme | undefined;
        as?: React.ElementType<any> | undefined;
    }, React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>
    

    So you could do:

    type BoxWithoutAs = StyledComponent<{
        theme?: Theme | undefined;
    }, React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
    
    const Box = BoxWithAs as BoxWithoutAs;
    

    But that type is really long and unwieldy. We try simplifying it and making it a little drier:

    StyledComponent<Omit<React.ComponentProps<BoxWithAsType>, "as">, React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>
    

    That's not much better and arguably worse. What would help is if we could infer the other two parameters instead of having us typing it in full.

    We can make use of a helper utility type:

    type OmitProps<Component, Props extends PropertyKey> = Component extends StyledComponent<infer P, infer S, infer J> ? StyledComponent<Omit<P, Props>, S, J> : never;
    

    Now, TypeScript will infer the other two parameters for us, and we can use Omit<P, Props> to omit any props we wish.

    const Box = BoxWithAs as OmitProps<BoxWithAsType, "as">;
    

    Seems to work fine!

    Playground