preactjs / preact-custom-element

Wrap your component up as a custom element
MIT License
360 stars 52 forks source link

Preact Component's constructor is not called when custom element is created via document.createElement #68

Open lpommers opened 2 years ago

lpommers commented 2 years ago

This is documented here in this comment from another PR: https://github.com/preactjs/preact-custom-element/pull/64#discussion_r770010488. document.createElement should run the component's constructor method.

class CounterPreact extends Component {
  constructor() {
    super()
    console.log('here')
  }
}

register(CounterPreact, 'x-counter-preact')

// ...

document.createElement('x-counter-preact') // no output
class CounterPlain extends HTMLElement {
  constructor() {
    super()
    console.log('here')
  }
}

customElements.define('x-counter-plain', CounterPlain)

// ...

document.createElement('x-counter-plain') // outputs here

The constructor only seems to fire when the custom element as appended to the DOM.

mikerob215 commented 2 years ago

I was curious about this behavior and looked into it a little bit. The reason is that making an instance of the web component does not make an instance of the preact component. That only happens when the preact component is rendered the first time, and the earliest that can happen is in the connectedCallback which runs when the element is appended. Its technically possible to render the preact component in the constructor but it would only work if also using the shadow dom as attaching the shadow creates a document fragment. Without a shadow root, the constructor doesn't have access to any dom yet and its unsafe to call render.

Heres a codesandbox to demonstrate: https://codesandbox.io/s/tender-shamir-dwk4zb?file=/src/index.js

It creates a preact component that logs in the constructor, and manually wraps it in 2 web components, one with a shadow root and one without. Both web components will console.log when calling document.createElement, but the one without the shadow root will cause an error.

So I think current behavior makes sense, but curious about any scenarios where you'd want both constructors to run at the same time.