Typescript error "Property does not exist on type 'JSX.IntrinsicElements'" when using native web component
Asked Answered
G

2

11

I'm working with a project that uses React and Typescript, but I want to start using native web components in my project to phase out some of my React components.

I'm getting this error when I try to include use a person-info component in some of my JSX.

Property does not exist on type 'JSX.IntrinsicElements'

I've looked at some of the other questions that have had these issues, but none of them seem to have anything to do with native web components in particular.

How do I get Typescript and React to play nicely when I use my web components in my project?

PersonInfo.mjs

const css = `
  <style>
    :host([hidden]) { display: none; }
    :host {
      align-items: center;
      display: grid;
      font-weight: normal;
      grid-gap: var(--spacing-size-a) var(--spacing-size-a);
      grid-template-areas:
        'picture heading'
        'picture sub-heading';
      grid-template-columns: auto 1fr;
      justify-items: start;
    }
    div {
      grid-area: picture;
    }
    h1, h2 {
      margin: 0;
      padding: 0;
    }
    h1 {
      align-self: end;
      font-size: var(--l-text-size);
      font-weight: normal;
      grid-area: heading;
      text-transform: capitalize;
    }
    h2 {
      align-self: start;
      font-size: var(--m-text-size);
      grid-area: sub-heading;
    }
    ion-icon {
      font-size: 56px;
    }
  </style>
`

const html = `
  <div>
    <ion-icon name="md-contact"></ion-icon>
  </div>
  <h1>Heading</h1>
  <h2>Sub-heading</h2>
`

class PersonInfo extends HTMLElement {
  static get observedAttributes () {
    return [
      'heading',
      'subHeading',
      'size'
    ]
  }

  constructor () {
    super()

    this.attachShadow({mode: 'open'})
    this.shadowRoot.appendChild(template.content.cloneNode(true))
  }

  connectedCallback () {
    this.shadowRoot.querySelector('h1').innerText = this.getAttribute('heading')
    this.shadowRoot.querySelector('h2').innerText = this.getAttribute('subHeading')
  }

  get heading () {
    return this.getAttribute('heading')
  }
  set heading (newValue) {
    this.setAttribute('heading', newValue)
    this.shadowRoot.querySelector('h1').innerText = newValue
  }

  get subHeading () {
    return this.getAttribute('subHeading')
  }
  set subHeading (newValue) {
    this.setAttribute('subHeading', newValue)
    this.shadowRoot.querySelector('h2').innerText = newValue
  }

  get size () {
    return this.getAttribute('size')
  }
  set size (newValue) {
    this.setAttribute('size', newValue)
  }
}

const template = document.createElement('template')
template.innerHTML = `${css}${html}`

window.customElements.define('person-info', PersonInfo)

Import statement

import '../../common/WebComponents/PersonInfo.mjs'

Usage in JSX

<main>
  <person-info
    heading='Bruce Wayne'
    subHeading="I'M BATMAN!"
  />
</main>
Gilroy answered 29/3, 2019 at 19:34 Comment(1)
Iv'e never created a web-component in my typescript / react project but i suspect if you augment JSX.IntrinsicElements and add personinfo with the type of its HTMLProps it will work. you can read about how to augment in the documentation.Inadmissible
G
27

I figured out after going here how to get this particular error to go away.

import * as React from 'react'

declare global {
    namespace JSX {
        interface IntrinsicElements {
            'person-info': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
        }
    }
}

I got another error after that, however, due to the custom attributes I use on my component. Thanks to Shanon's comment, I figured out how to fix that too and ended up with this final code that I just imported in my App.tsx file.

import * as React from 'react'

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'person-info': PersonInfoProps
    }
  }
}

interface PersonInfoProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> {
  heading: string,
  subHeading: string,
  size?: string
}
Gilroy answered 29/3, 2019 at 20:3 Comment(4)
But it raises another error for React app creates with CRA: "ES2015 module syntax is preferred over custom TypeScript modules and namespaces @typescript-eslint/no-namespace"Kinghorn
It's true, that if you use this rule in your eslint, it does raise an error, though typescript itself has no issues with it. The reason this fix works is because it extends the existing namespace for JSX to include any custom web components. I don't see another way to solve the issue outside of modifying the original definition of JSX itself (which I think is a bad idea). If you want to keep the @typescript-eslint/no-namespace rule active for the rest of the repository while making sure this works, I'd recommend ignoring the eslint rule for just this file.Gilroy
I have the same eslint warning and looking at the issue, it seems that this syntax is in the way of being deprecated from typescript. Unfortunatelly, I am not able to find a working syntax. Does anyone better in TS than me as a solution ?Adahadaha
I just added 'person-info' and the JSX declaration. Adding <person-info /> inside my preact component still fails. Is there any major difference between how react and preact handle this? I have types for @types/react and "jsx": "react", in typesccript config like suggested elsewhere but not luck.Begley
N
0

If anyone is getting the same error with Preact you can take a similar approach as the accepted answer, but the difference is that JSX sits under the preact namespace.

declare global {
  namespace preact {
    namespace JSX {
      interface IntrinsicElements {
        'person-info': {
          // your props here
        }
      }
    }
  }
}
Nucleon answered 8/6 at 22:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.