react: Bug: Reconciler cannot handle Declarative Shadow DOM (DSD)

The reconciler does not ignore <template shadowRoot="open"> but handles them like a normal HostElement. In reality, as soon as the closing template tag is parsed, the component is replaced in the DOM by #shadow-root (open)

See: https://github.com/mfreed7/declarative-shadow-dom#-behavior

React version: 18.2.0

Steps To Reproduce

I tried this with NextJS 13.1.6, which uses react 18.2.0 and react-dom 18.2.0.

In the end the component is rendered server side and hydrated in the frontend.

  1. Add the following html code to your component
<div>
  <template shadowrootmode="open"> 
    <button type="button">
      <slot></slot>
    </button>
  </template>
  My button
</div>  
  1. Render the component via SSR and hydrate in the frontend.

The current behavior

A hydration warning is thrown:

Expected server HTML to contain a matching <template> in <div>.
    at template
    at div
    at Home 

The expected behavior

In the end i guess a DSD should be handled as an isolation block where on the server the DSD template tag is allowed, but on the client hydration all children of the block are hydrated to the now existing ShadowRoot.

Server -> Render template tag Client -> Children of template tag are hydrated against the ShadowRoot fragment.

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 15
  • Comments: 15

Most upvoted comments

Note that it’s now shadowrootmode and not shadowroot.

I tried this in the latest version of both Remix and Next.js. It’s definitely an issue.

@jonathandewitt-dev

I’m currently working on updating my NextJS/WebComponent Repo by getting a StencilJS component working

Once this is done i will have a look on what needs to be adjusted in react-dom so i do not have to handle server and client differently because of the shadowRoot template tag, as seen here.

It is about the core concept.

Server -> Render tag, DSD and component nodes Client -> Hydrate component nodes using existing ShadowRoot.

There is no reason the handle DSD on the client at all. At least not the tag, but React must replace it on client hydration with the ShadowRoot as the hydration root.

It is also not correct as i wrote in my initial post here that React should ignore the <template shadowroot="open"> tag and all its children

Cases like having an onClick here should of course still work:

<div>
  <template shadowrootmode="open">  // If we ignore everything from the DSD onwards, onClick in the next line will not hydrate.
    <button type="button" onClick={handleClick}>
      <slot></slot>
    </button>
  </template>
  My button
</div>  

The isolation of WebComponents and hydrating only specific ShadowRoots helps here. Meaning you can render something for a component on the server (tag, dsd, content) and use a different render/hydration target and just the component content on the client.

The only solution i could think of would be that React handles DSD <template> tags as an isolation block, meaning on hydration it searches for an ShadowRoot fragment in place of the DSD and hydrates all the children to that ShadowRoot.