Definitelytyped: Extending styled-components interface with static properties

Created on 10 Jan 2019  路  2Comments  路  Source: DefinitelyTyped/DefinitelyTyped

I am trying to add properties to the constructor produced by styled components using TypeScript.

What I want to do:

type JSXButtonProps = JSX.IntrinsicElements['button'];

export interface ButtonProps extends JSXButtonProps {
  theme?: ThemeBase;
}

const ButtonInner = ({ theme, ...rest }: ButtonProps) => <button {...rest} />;

export const Button = styled(ButtonInner)(
  (props: ButtonProps): CSSEntry => get(props.theme, 'button.extend'),
);

Button.Primary = props => <Button />;

Which produces the error:

error TS2339: Property 'Primary' does not exist on type 'StyledComponent<FunctionComponent<ButtonProps>, ThemeBase, {}, never>'.

What works (casting to any):

export const Button = styled(ButtonInner)(
  (props: ButtonProps): CSSEntry => get(props.theme, 'button.extend'),
);

(Button as any).Primary = props => <Button />;

I'd like to avoid this, so I looked into re-creating my own ButtonInterface, but the typings are so complex, I can't figure out where to start. For instance I see StyledComponent<...> but this is a generic type which cannot be extended, so that won't work. I've tried making the type and joining in the additional static class properties, but that hasn't worked either.

My latest attempt has been to do:

interface ButtonAliases extends StyledComponentBase<any, any, any, any> {
  Primary: any;
  Secondary: any;
  Tertiary: any;
  Icon: any;
}

export const Button: ButtonAliases = styled(ButtonInner)(...);

But this does not work either, I get the error:

error TS2322: Type 'StyledComponent<({  theme, ...rest }: ButtonProps) => Element, ThemeBase, {}, never>' is not assignable to type 'ButtonAliases'.

Any help or suggestions would be great, thank you!

Most helpful comment

A workaround, but you get type-checking for static properties:

import { Header } from './Header';
import { Details } from './Details';
import { InformationIcons } from './InformationIcons';

const Tile = styled.div`
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
`;

const InvestmentTile = Tile as typeof Tile & {
  Details: typeof Details;
  InformationIcons: typeof InformationIcons;
  Header: typeof Header;
};

InvestmentTile.Details = Details;
InvestmentTile.InformationIcons = InformationIcons;
InvestmentTile.Header = Header;

The only issue (besides extra work) is that if you forget to do:

InvestmentTile.Details = Details;
InvestmentTile.InformationIcons = InformationIcons;
InvestmentTile.Header = Header;

You won't get any errors.

All 2 comments

@tbranyen, the best I've been able to come up with so far is:

const Styled = styled(Button)``;

Object.defineProperty(Styled, 'Action', { value: Action });
Object.defineProperty(Styled, 'Minimal', { value: Minimal });

export default Styled;

I'd love to hear if you've come up with some thing better than that because I really don't like it...

A workaround, but you get type-checking for static properties:

import { Header } from './Header';
import { Details } from './Details';
import { InformationIcons } from './InformationIcons';

const Tile = styled.div`
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
`;

const InvestmentTile = Tile as typeof Tile & {
  Details: typeof Details;
  InformationIcons: typeof InformationIcons;
  Header: typeof Header;
};

InvestmentTile.Details = Details;
InvestmentTile.InformationIcons = InformationIcons;
InvestmentTile.Header = Header;

The only issue (besides extra work) is that if you forget to do:

InvestmentTile.Details = Details;
InvestmentTile.InformationIcons = InformationIcons;
InvestmentTile.Header = Header;

You won't get any errors.

Was this page helpful?
0 / 5 - 0 ratings