Mister Epic
Mister Epic

Reputation: 16733

Infer Prop interface from styled-component

I try to infer my components' Props interface over exporting them whenever possible. This isn't an issue with class and functional components, but if I try to infer the Props interface of a styled-component, the prop is typed to any which is not ideal.

interface Props {
  bgColor: string;
  children: React.ReactNode;
}

const Box = styled.div<Props>`
  background-color: ${(p) => p.bgColor};
`;

const Button = (props: Props) => (
  <button style={{ backgroundColor: props.bgColor }}>{props.children}</button>
);

type ButtonInferredProps = React.ComponentProps<typeof Button>;

type BoxInferredProps = React.ComponentProps<typeof Box>;

const OtherBox = (props: BoxInferredProps) => (
  <div style={{ backgroundColor: props.bgColor }}>{props.children}</div>
);

const OtherButton = (props: ButtonInferredProps) => (
  <button style={{ backgroundColor: props.bgColor }}>{props.children}</button>
);

export default function App() {
  return (
    <>
      <Box bgColor="red">Hi! I'm a box! </Box>
      <OtherBox bgColor="purple" backgroundColor="red">
        Hi! I'm another box
      </OtherBox>
      <Button bgColor="blue">Hi! I'm a button</Button>
      <OtherButton bgColor="green">Hi! I'm another button</OtherButton>
    </>
  );
}

With Box being a styled-component, I can't properly infer its Props interface. When I create another component that uses attempts to use the inferred Props type, it comes through as any:

const OtherBox = (props: BoxInferredProps) => (
  {/* TS won't complain that `props` doesn't have a `iAmNotTyped` property which is desired... */}
  <div style={{ backgroundColor: props.iAmNotTyped}}>{props.children}</div>
);

https://codesandbox.io/s/styled-components-typescript-forked-7cq4q?file=/src/App.tsx

Upvotes: 9

Views: 2022

Answers (4)

M K
M K

Reputation: 9416

I've tried various examples in this thread and found that none of the proposed solutions work for all the use cases I have in mind.

I want to be able to infer props of these kinds of components:

  1. Regular component
  2. Styled component
  3. Styled component with custom props
  4. Vanilla components, like div, input, etc.

I was able to cover all the use cases with this type helper:

import { ComponentPropsWithoutRef, JSXElementConstructor } from "react"
import {
  AnyStyledComponent,
  StyledComponentInnerComponent,
  StyledComponentInnerOtherProps,
} from "styled-components"

export type InferComponentProps<T> =
  T extends AnyStyledComponent
    ? ComponentPropsWithoutRef<StyledComponentInnerComponent<T>> &
        StyledComponentInnerOtherProps<T>
    : T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
    ? ComponentPropsWithoutRef<T>
    : never

Now you get correct prop types for all of these use cases:

const StyledComponent = styled.input ``
const AdvancedStyledComponent = styled.input<{ customProp: boolean }> ``
const RegularComponent = (props: { customProp: boolean }) => null

InferComponentProps<typeof StyledComponent>
InferComponentProps<typeof AdvancedStyledComponent>
InferComponentProps<typeof RegularComponent>
InferComponentProps<"input">

Upvotes: 1

Ramon Balthazar
Ramon Balthazar

Reputation: 4260

styled-components exports a Typescript helper for this.

It's called StyledComponentPropsWithRef:

import React from 'react';
import styled, { StyledComponentPropsWithRef } from 'styled-components';

const RedLink = styled.a`
  color: red;
`;

type Props = StyledComponentPropsWithRef<typeof RedLink>;

const Link = (props: Props) => <RedLink {...props} />;

export default function App() {
  return <Link href="/" role="button" />;
}

This code works ☝️

Upvotes: 6

Linda Paiste
Linda Paiste

Reputation: 42218

The type for your Box styled component is showing up as StyledComponent<"div", any, Props, never>. So you can make use of typescript's infer keyword to extract Props from that type definition.

import {StyledComponent} from "styled-components";

type StyledComponentProps<T> = T extends StyledComponent<any, any, infer P, any> ? P : never

type BoxInferredProps = StyledComponentProps<typeof Box>;

Now you get a typescript error, as expected, when trying to assign backgroundColor on OtherBox.

Upvotes: 3

Hassan Naqvi
Hassan Naqvi

Reputation: 1061

You can define a type like this:

import {
 StyledComponentInnerComponent,
 StyledComponentInnerOtherProps,
 AnyStyledComponent
} from 'styled-components';

type inferStyledTypes<T extends AnyStyledComponent> = 
  React.ComponentProps<StyledComponentInnerComponent<T>>
  & StyledComponentInnerOtherProps<T>;

// Use the above type to infer the props:
type BoxInferredProps = inferStyledTypes<typeof Box>

Upvotes: 2

Related Questions