styled-components defaultProps

Viewed 14261

If I have the following button with a defaultProp

export interface IButton {
  variant: 'action' | 'secondary';
}

export const Button = styled('button')<IButton>`
  background-color: #fff;

  ${props =>
    props.variant === 'action' &&
    css`
      color: blue;
    `};

  ${props =>
    props.variant === 'secondary' &&
    css`
      color: gray;
    `};
`;

Button.defaultProps = {
  variant: 'action',
};

Is there a way to type it? When trying to use it like

<Button>Hello</Button>

Typescript complains about not passing variant, is there a way to type defaultProps with styled components?

4 Answers

The problem is that TypeScript 3.0's support for defaultProps in checking JSX elements requires the type of the defaultProps to be declared on the component. Mutating the defaultProps of an existing component won't work, and I'm not aware of any good way to declare the defaultProps on a component generated by a function like styled. (In a way, this makes sense: the library creates a component and doesn't expect you to modify it. Maybe the library even sets defaultProps itself for some internal purpose.) kingdaro's solution is fine, or you can use a wrapper component:

const Button1 = styled('button')<IButton>`
  background-color: #fff;

  ${props =>
    props.variant === 'action' &&
    css`
      color: blue;
    `};

  ${props =>
    props.variant === 'secondary' &&
    css`
      color: gray;
    `};
`;

export class Button extends React.Component<IButton> {
  static defaultProps = {
    variant: 'action'
  };
  render() {
    return <Button1 {...this.props}/>;
  }
}

You can achieve what you want by destructuring your props.

It seems that you still have to let your component know about its prop types. For that, just pass all props without destructuring them (see background-color down below).

import styled from "styled-components";

interface IProps {
  variant?: 'action' | 'secondary';
}

export const Button = styled.div`
  ${(props: IProps) => `background-color: #fff;`}
  ${({ variant = 'action' }) => variant === 'action' ? `color: blue;` : `color: gray;`}
`;

To my knowledge, this isn't quite possible yet, and unfortunately isn't covered by the defaultProps support added in TS 3.0 (that only applies to normal component classes, and I think functional components). Others feel free to correct me if I'm wrong on this.

There are other ways to write it, though. Here's how I usually go about doing it:

export interface IButton {
  variant?: 'action' | 'secondary';
}

const variantStyles = {
  action: css`
    color: blue;
  `,
  secondary: css`
    color: gray;
  `,
};

export const Button = styled('button')<IButton>`
  background-color: #fff;
  ${props => variantStyles[props.variant || 'action']};
`;

For most cases I use a wrapper component with default values:

interface IProps {
    // not optional
    variant: 'action' | 'secondary';
}

const SButton = styled.button<IProps>`
    ...styles
`

// make optional
export const Divider = (props: Partial<IProps>) => {
    // set default value
    const { variant = 'action', ...rest } = props;

    // insert default value
    return <SButton variant={variant} {...rest}>{children}</SButton>;
};

Also better to separate styled props and main component props

interface IProps extends Partial<IStyledProps> {
    // component props
}

// or if you have composed component with multiple styled components
interface IProps extends Partial<IStyledProps1>, Partial<IStyledProps2>, Partial<IStyledProps3> {
    // component props
}
Related