question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

webcomponents: unnecessary wrappers

See original GitHub issue

Describe the bug The outcome produces a builder-component which wouldn’t exist in the React counterpart, meaning that each component will result in DOM bloat for no reason, nor benefits.

To Reproduce Steps to reproduce the behavior:

  1. visit this demo example
  2. inspect the DOM
  3. note builder-component wraps the DIV, which is the only thing React would render instead

Expected behavior A div builtin extend that is="builder-component-xxx" where xxx is the unique relation between the MyComponent function and the outcome in the DOM.

Additional context The only way to represent React output as Custom Element is to use builtin extends, available in every browser but Safari, which can easily be polyfilled if necessary in case window.safari or window.webkit exists, through import maps or ad-hoc builds for these targets.

The output should be a custom elements which represents what the React function would return and update its state/view whenever a state is triggered.

This is because it’s impossible otherwise to represent special nodes such as td, li, tr, option, or others, if these get wrapped by unrelated nodes.

Changing div with td as returned node will indeed fail.

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:9 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
WebReflectioncommented, Apr 20, 2022

@samijaber thanks! About the example, this is just a quick summary of what should be done, but it requires special care for props and it doesn’t include fragments possibility. Bear in mind 1:1 React to WC is extremely difficult to have, simply because the paradigm is different, and so are the primitives used behind the scene.

React

import { useState } from "@builder.io/mitosis";

export default function MyComponent(props) {
  const [name, setName] = useState("Steve");

  return (
    <div value={props.name} onClick={(event) => console.log(event.type)}>
      <input
        css={{
          color: "red",
        }}
        value={name}
        onChange={(event) => setName(event.target.value)}
      />
      Hello! I can run in React, Vue, Solid, or Liquid!
    </div>
  );
}

WC

/**
 * Usage:
 *
 *  <div is="my-component"></div>
 *
 */
 class MyComponent extends HTMLDivElement {
  constructor() {
    super();

    // needed only if global style is needed/used (see connectedCallback)
    this.dataset.name = 'div-1';

    this.state = { name: "Steve" };

    // Event handler for 'click' event on div-1
    this.onDiv1Click = (event) => {
      console.log(event.type);
    };

    // Event handler for 'input' event on input-1
    this.onInput1Input = (event) => {
      this.state.name = event.target.value;
      this.update();
    };
  }

  // currently, the injected style would affect all inputs out there
  // so, there are two different possible CSS solutions for the input:
  //  * inline style (faster, simpler, easier to implement)
  //  * global style with a specifier:
  //      <style>
  //        div[is="my-component"][data-name="div-1"] input {
  //          color: red;
  //        }
  //      </style>
  connectedCallback() {
    this.innerHTML = `
        <input style="color: red;" data-name="input-1" />

        Hello! I can run in React, Vue, Solid, or Liquid!
    `;
    this.update();
  }

  update() {
    // the values related to the element itself should be addressed directly
    this.value = this.props.name;
    this.removeEventListener("click", this.onDiv1Click);
    this.addEventListener("click", this.onDiv1Click);

    // values related to inner nodes should, instead, be added at runtime
    this.querySelectorAll("[data-name='input-1']").forEach((el) => {
      el.value = this.state.name;
      el.removeEventListener("input", this.onInput1Input);
      el.addEventListener("input", this.onInput1Input);
    });
  }
}

customElements.define("my-component", MyComponent, {extends: 'div'});
// the render should now create a <div is="my-component"> instead
// and it should attach right after `props` to it

Now, with this kind of outcome, we can represent pretty much every JSX except for fragments, which cannot really be represented as single WC because we can’t extend, or define, a fragment in a declarative way, so that use case might require a special <partial-content> Custom Element that takes its previous node and append to it its content.

Last, but not least, it should be possible to have inner components through “virtual slots” injected once with the innerHTML on connected callback, and updated later on each time it’s needed (propagate updates down those references).

So, updates propagates down but there’s no need to propagate up, except for events that bubble already so … it’s all good.

I hope this give you a better idea of what I mean when I say that currently mitosis does not provide React to WC conversion.

1reaction
WebReflectioncommented, Apr 15, 2022

this is what I mean: the outcome creates a wrapper of what React would’ve created …

  • React: it renders a div
  • mitosis: it renders a custom element that contains the div React would’ve rendered so it adds an extra layer/element per each translated component

nope

nope-nope

This does not work when components cannot be wrapped, as it is for <table>, <td>, <li>, <option> and other standrd HTML element otherwise possible with React.

Indeed, this example/demo is not possible with current mitosis webcomponents target, but if it is, please show me that exact demo outcome after mitosis, where the rendered output should be exactly this one, as it is for its React counterpart:

<body>
  <table>
    <tr>
      <th>Name</th>
      <th>Age</th>
    </tr>
    <tr>
      <td>Custom Element</td>
      <td>8</td>
    </tr>
  </table>
</body>

I hope this is now clear.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why I don't use web components - DEV Community ‍ ‍
Web Components IMHO should have a framework wrapper to make problems like you talk about go away. That doesn't make them bad, ...
Read more >
Web Components Are Easier Than You Think | CSS-Tricks
The <template> element is important because it holds things together. It's like the foundation of building; it's the base from which everything ...
Read more >
The Failed Promise of Web Components - Hacker News
Here's what killed web components: lack of native databinding on the web. That's the reason the standard is useless without JS.
Read more >
Handling data with Web Components | by Andreas Remdt
Web Components, or rather custom elements, have a similar concept ... To avoid unnecessary re-renders, it's a best practice to check if the ......
Read more >
Non-class based example of customElement.define() #587
The new version is here: https://github.com/webcomponents/custom-elements/blob/master ... which many people argued are unnecessary and ugly.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found