Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using styled components "as" prop with typescript

I'm currently building a pattern library in which I've built a Button component using React and styled-components. Based on the Button component, I want all my Links component to look exactly the same and receive exactly the same props. For that purpose, I'm using the as prop from styled-components, which allows me to use that already built element as another tag or component.

Button Component

import * as React from 'react'
import { ButtonBorderAnimation } from './ButtonAnimation'
import { ButtonProps, ButtonVariant } from './Button.types'
import { ButtonBase, LeftIcon, RightIcon } from './Button.styled'

function Button({
  variant = ButtonVariant.Filled,
  children,
  leftIcon = null,
  rightIcon = null,
  ...props
}: ButtonProps): JSX.Element {
  return (
    <ButtonBase variant={variant} {...props}>
      {variant !== ButtonVariant.Ghost ? (
        <ButtonBorderAnimation {...props} />
      ) : null}
      {leftIcon ? <LeftIcon>{leftIcon}</LeftIcon> : null}
      {children}
      {rightIcon ? <RightIcon>{rightIcon}</RightIcon> : null}
    </ButtonBase>
  )
}

export default Button

Button Types

export interface ButtonProps {
  children: React.ReactNode
  variant?: 'filled' | 'outlined' | 'ghost'
  size?: 'small' | 'regular'
  underlinedOnHover?: boolean
  leftIcon?: React.ReactNode
  rightIcon?: React.ReactNode
  inverse?: boolean
}

export enum ButtonVariant {
  Filled = 'filled',
  Outlined = 'outlined',
  Ghost = 'ghost',
}

export enum ButtonSize {
  Small = 'small',
  Regular = 'regular',
}

Link Component

import * as React from 'react'
import Button from '../Button/Button'
import { Link as LinkRouter } from 'react-router-dom'
import { LinkProps } from './Link.types'

function Link({ to, ...props }: LinkProps): JSX.Element {
  return <Button to={to} as={LinkRouter} {...props} />
}

export default Link

Link Types

import { ButtonProps } from '../Button/Button.types'
import { LinkProps } from 'react-router-dom'

type RouterLinkWithButtonProps = ButtonProps & LinkProps

export interface LinkProps extends RouterLinkWithButtonProps {}

When I do the above, this problem comes us...

Property 'to' does not exist on type 'IntrinsicAttributes & ButtonProps'.

...which makes sense because button doesn't have the prop to which is required for the Link component from react-router-dom.

How will you approach something like this? Where when using the Button the to prop shouldn't even be in the type and when using the Link the to should be required.

Uses

<Button>Hello</Button>
<Link to="/somewhere">Hello</Link>
like image 385
Alejandro Garcia Anglada Avatar asked Jul 01 '19 14:07

Alejandro Garcia Anglada


People also ask

Can I use styled-components with TypeScript?

This installs the styled-components types for TypeScript as a dev dependency. There's a styled-components extension for VS Code that makes it look like we're typing actual CSS even though it's a TypeScript file. It's called vs-code-styled-components and I suggest you install it before proceeding if you haven't already.

What is a prop in TypeScript?

Props are used for components in react, but the syntax will be vary depending on the type of the component. For the following examples, we will use them in functional components. At the end of the blog we will do the same examples but with class components.


1 Answers

This should work.

function Link(_props: LinkProps): JSX.Element {
  const props = { as: LinkRouter, ..._props };
  return <Button {...props} />
}

Note that TypeScript applies strict object literal assignment checks which can be loosened by assigning an object literal to a variable beforehand instead of directly passing it as, for example, an function argument, which is consistent with the React component props assignment behavior.

declare function foo(arg: { a: number }): void;

foo({ to: '', a: 1 }); // error

const arg = { to: '', a: 1 };
foo(arg); // no error
like image 157
kimamula Avatar answered Sep 20 '22 17:09

kimamula