bitovi / react-to-web-component

Convert react components to native Web Components. Works with Preact too!
https://www.bitovi.com/open-source/react-to-web-component
MIT License
675 stars 41 forks source link

Accessing `children` #106

Open jacobjuul opened 1 year ago

jacobjuul commented 1 year ago

Let's say I have this webcomponent

<my-component>some text I want access to in react</my-component>

I assumed the text would be available as children in the react component, but it's not as far as I can tell. Can I access this.textContent somehow instead?

christopherjbaker commented 1 year ago

We explored a few options for direct access, but the only option we found entailed crawling the DOM children and turning them into React nodes; while functional, it was very messy and create a lot of React key errors, so it wasn't really a viable solution.

If you just want to render that text, and don't need access to the value, slots are what you want. Given how you've written the above component, you can render that text anywhere you want by rendering <slot></slot> (as many times as you want). If you want "fallback" content to show if they use doesn't provide a value, you can put that inside the slot tag. If you want multiple of these user-defined content blocks, you can use named slots, too.

<my-component>
  <span slot="content1">some text I want access to in react</span>
  <p slot="content2">some other content I want access to in react</p>
</my-component>

Then render these with <slot name="content1"></slot> and <slot name="content2"></slot>.

If you need direct access to the value, I would suggest putting it in an attribute on the WC, or setting it on a property after the component is mounted.

christopherjbaker commented 1 year ago

If you think this would benefit from further discussion, or if not, I'd invite you to join our community discord. There are quite a few threads there about R2WC, and t's become a really great spot to ask questions, share knowledge, and connect about other interests too.

jacobjuul commented 1 year ago

Thanks so much. I actually ended up solving it with a slot although not exactly what I wanted.

christopherjbaker commented 1 year ago

If you can explain your needs a bit more, I might be able to incorporate something into a future version that solves the problem better.

rowthan commented 1 year ago

I solved as follow: this code worked in react component.

const slotName = 'children' //  set a slot name id whatever you like.
const {children} = props; // this children is comes from React children property original.
useEffect(function() {
    // pick all children from custom element and set a slot property for them.
    const children: HTMLElement[] = props.container?.host?.children;
    if(children){
      for(let i=0; i<children.length; i++){
        children[i].setAttribute('slot', slotName)
      }
    }
  },[])

  return (
    <div>
       {children}
      // set a slot for html element.
       <slot name={slotName}></slot>
    </div>
  )

how about put this logic into React-to-web-component? i think it won't cause some issue like React key error?

rowthan commented 1 year ago

what cause this case is confused me. i did not meet this case while creating custom element by svelte. so what's the problem? is it a limited by web component or svelte has solved this issue in some way?

rowthan commented 1 year ago

I have got the reason in this code. it's not a limit by custom element but React.

React render need a root element as the root. so you provide this(custom-element) as its root. so after render, the origin children element by html will be replaced.


if (options.shadow) {
    this.container = this.attachShadow({
      mode: options.shadow,
    }) as unknown as HTMLElement
  } else {
    this.container = this
  }

this[contextSymbol] = renderer.mount(
      this.container,
      ReactComponent,
      this[propsSymbol],
    )

To avoid prevent this issue. how about select or create a root instead of this? like bellow:

let rootContainer = this;
const thisCustomElement = this;

// 1. check if `thisCustomElement ` has a some children
const hasChildren = this.CustomEelement.children.length > 0;
if(hasChildren){
  const childRoot = document.createElement('div');
  thisCustomElement.appendChild(childRoot);
  rootContainer = childRoot;
}
rowthan commented 1 year ago

I have got the reason in this code. it's not a limit by custom element but React.

React render need a root element as the root. so you provide this(custom-element) as its root. so after render, the origin children element by html will be replaced.


if (options.shadow) {
    this.container = this.attachShadow({
      mode: options.shadow,
    }) as unknown as HTMLElement
  } else {
    this.container = this
  }

this[contextSymbol] = renderer.mount(
      this.container,
      ReactComponent,
      this[propsSymbol],
    )

To avoid prevent this issue. how about select or create a root instead of this? like bellow:

let rootContainer = this;
const thisCustomElement = this;

// 1. check if `thisCustomElement ` has a some children
const hasChildren = this.CustomEelement.children.length > 0;
if(hasChildren){
  const childRoot = document.createElement('div');
  thisCustomElement.appendChild(childRoot);
  rootContainer = childRoot;
}

@christopherjbaker check this please.

rowthan commented 1 year ago

replace all origin children or create a new child root node maybe could be a property choice when using custom html. both of then are useful. one is need keep origin node as children. another don't need keep it can be used as a fallback on connected error or custom html not work.

TheRarita commented 1 year ago

I started conversation on this topic a while ago on Discord as well, here is the link for the thread for reference: https://discord.com/channels/1007137664606150746/1113836418716942358/1113836418716942358

duoduoObama commented 10 months ago

I have also encountered this problem. This problem is too critical. If children cannot include nested components, and do this in attributes, the converted components will be quite troublesome to use.

guenyoo commented 6 months ago

That's a usecase we would need as well for web-components. An Accordeon-Component for example doesn't need to know it's children beforehand and it would be necessary to just be able to drop other components/content in.

Is there a workaround that works in the meantime?