socketsupply / tonic

A Low Profile Component Framework – Stable, minimal, easy to audit, zero-dependencies and build-tool-free.
http://tonicframework.dev
MIT License
867 stars 24 forks source link

"Uncaught DOMException: Operation is not supported" when assigning id in the constructor #68

Closed austinfrey closed 3 years ago

austinfrey commented 3 years ago

Do you want to request a feature or report a bug?

Bug

Reproduce with the following snippet

const Tonic = require('@optoolco/tonic')
const bundle = require('@optoolco/components')

bundle(Tonic)

class MyApp extends Tonic {
  constructor(){
    super()
    this.id = 'my-app'
  }
}
<head>
  <script src="bundle.js"></script>
</head>
<body>
  <my-app></my-app>
</body>

Describe the bug

When assigning an element id in the constructor, and error is thrown: Uncaught DOMException: Operation is not supported.

This seems to only happen for the root element as assigning an in child elements seems to work fine.

Expected behavior When assigning an id attribute in the root element constructor, the id should be assigned to the element.

Actual behavior When assigning an attribute in the root element constructor, and uncaught exception is thrown: Uncaught DOMException: Operation is not supported

Desktop (please complete the following information):

Raynos commented 3 years ago

This seems to only happen for the root element as assigning an in child elements seems to work fine.

Web components have two different ways of running the constructor. When you call Tonic.add(MyApp) it will find all existing DOM elements in the document that match my-app and call the web component constructor / lifecycles.

If you create html after calling Tonic.add(...) for any future elements inserted into the DOM then it will call those constructor / life cycles in a subtly different way. This explains why it impacts the root element.

Uncaught DOMException: Operation is not supported

We do the following as a work around

  <my-app id="my-app"></my-app>

The only time you need to set the id in the constructor is when you create a tonic component using new MyApp()

We recommend you set id as a HTML attribute.

Raynos commented 3 years ago

Updating documentation in https://github.com/optoolco/components/pull/96

austinfrey commented 3 years ago

@Raynos thank you for those details. very helpful.

I run into a second issue when applying the id as an HTML attribute and wanting to use state: Uncaught Error: Component: ...<my-app></my-app> has no id

const Tonic = require('@optoolco/tonic')
const bundle = require('@optoolco/components')

bundle(Tonic)

class MyApp extends Tonic {
  constructor(){
    super()
    this.state = {}
  }
}
<head>
  <script src="bundle.js"></script>
</head>
<body>
  <my-app id="my-app"></my-app>
</body>
Raynos commented 3 years ago

@austinfrey

That's a web components limitation unfortunately.

You have the script tag in <head> instead of at the end of <body>. The constructor for my-app gets called and this.getAttribute('id') is null, apparently attributes are not available in the constructor with that order of execution.

Moving the script tag as the last DOM element in <body> fixes it.

Alternatively you can have an empty <body> and use const app = new MyApp(); document.body.appendChild(app) in the bundle.js itself.

austinfrey commented 3 years ago

@Raynos ahh perfect. thanks so much :)