skip to Main Content

How can JSDoc be used to type props of styled-components in combination with tsconfig.json and tsc.

In TypeScript this is very easy with a simple generic, unfortunately I cannot achieve the same using JSDoc. I would imagine doing something like this.

/** @type {import('styled-components').styled["div"]<{ $open: boolean }>} */
const ChildContent = styled.div`
  animation: ${p => p.$open ? slideOpen : slideClose};
`

I noticed that StyleFunction works and the props can be provided using the generic. However, this type does not include any information about the component, only the props of the component. So the code below does not show any errors, until it’s used with for instance a ref property.

/** @type {import("styled-components").StyleFunction<{ $open: boolean }>} */
const ChildContent = styled.div`
  animation: ${p => p.$open ? slideOpen : slideClose};
`

// breaks here, 'ChildContent' cannot be used as a JSX component.
const Component = <ChildContent style={{ position: "absolute" }} $open={show} />

So another possible solution would be to combine this with another generic that takes the Runtime and Target. But, even better would be to directly access the function generic in Styled (exported as StyledInstance).

Big thanks for anyone who can help find the correct type.

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to Jesus Jemenez Cordero I better understood the StyledInstance type. And with some type inference it is possible to create a simple type and add that to a .d.ts file.

    Here are the steps I took to get to the solution: (Step 4 and 5 contain the final code)

    1. Use the StyledInstance type to get the correct type, the final generic parameter is required.
    /**
     * @typedef {import('styled-components').StyledInstance<"web", "div", { $open: boolean }, {}>} DivComponent
     */
    
    1. You will need to cast the function for the props to be detected in the template literal.
    const ChildContent = /** @type {DivComponent} */ (styled.div)`
      animation: ${p => p.$open ? slideOpen : slideClose};
    `
    
    1. Now you will find that all the HTML attributes are not available anymore. The easiest solution is to copy the type from the styled.d.ts file generated in the package.
    /**
     * @typedef {import('styled-components').StyledInstance<"web", "div", { $open: boolean } & import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>} DivComponent
     */
    
    1. But this is anoying to do, it would be much better if the Target, HTML OuterProps and OuterStatics are already filled in. So create a .d.ts file with that can extract these automatically.
    type GetComponent<Target extends import("styled-components").WebTarget> = import("styled-components").Styled[Target]
    type GetComponentHtmlProps<Component> = Component extends import("styled-components").StyledInstance<"web", any, infer X, any> ? X : any
    
    type StyledComponent<Target extends import("styled-components").WebTarget, Props extends object> = import("styled-components").StyledInstance<
      "web", Target,
      Props & GetComponentHtmlProps<GetComponent<Target>>,
      {}
    >
    
    1. You can simply use the StyledComponent type to add props to the type.
    /**
     * @typedef {StyledComponent<"div", { $open: boolean }>} DivComponent
     */
    const ChildContent = /** @type {DivComponent2} */ (styled.div)`
      animation: ${p => p.$open ? slideOpen : slideClose};
    `
    
    1. I added the .d.ts file to the root of my project (outside the src folder). Make sure the file is added to the TypeScript compiler like so.
    {
      "files": [
        "./types.d.ts"
      ]
    }
    

    Unfortunately this is an incomplete answer. The answer at least misses extending existing styled-components using the styled(MyComponent) syntax.


  2. Sound like your are missing a couple of properties on your tcsconfig.json. If I’m not wrong, to integrate JSDoc you need to add this props if you want to have al JSDoc working correctly.

    {
      "compilerOptions": {
        "allowJs": true,
        "checkJs": true,
        "noEmit": true,
         ... rest of your configuration
      },
      ... rest of your configuration
    }
    

    And your component and types maybe need to look something similar to this instead:

    /**
     * @typedef {Object} Props
     * @property {boolean} $open
     */
    
    /**
     * @type {import('styled-components').StyledComponent<'div', any, Props, never>}
     */
    const ChildContent = styled.div`
      animation: ${p => p.$open ? slideOpen : slideClose};
    `;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search