lambda-packages icon indicating copy to clipboard operation
lambda-packages copied to clipboard

🐛 BUG: Astro + Solid. Hydration issue in nested islands.

Open thetarnav opened this issue 3 years ago • 5 comments

What version of astro are you using?

1.0.0-beta.72

Are you using an SSR adapter? If so, which one?

doesn't apply

What package manager are you using?

pnpm

What operating system are you using?

Windows

Describe the Bug

I'm using Astro + Solid on my app and I think I might have found a hydration bug. It's a weird edge case involving nested islands but I'm using this pattern quite a lot. It's pretty much this:

// Solid components
function WrapperA() {
  return (
    <div>
      <Counter type="A"></Counter>
    </div>
  );
}
function WrapperB(props) {
  return (
    <div>
      <div>{props.children}</div>
      <Counter type="B"></Counter>
    </div>
  );
}
function Counter(props) {
  const [count, setCount] = createSignal(0);
  return (
    <button onClick={() => setCount((p) => ++p)}>
      {count()}
    </button>
  );
}

// Astro component:
<WrapperB client:load>
  <WrapperA client:load/>
</WrapperB>

In this setup, the WrapperB and his Counter inside aren't getting hydrated properly. The component is executed client-side, but with weird artifacts. For example, the ref prop accepts a callback that will be triggered when the component mounts to the dom — but here, the callback is getting called with the wrong element. The rest of the props, like event listeners aren't being applied correctly also.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/astro-solid-bug?file=src%2Fpages%2Findex.astro&on=stackblitz

Participation

  • [ ] I am willing to submit a pull request for this issue.

thetarnav avatar Jul 17 '22 08:07 thetarnav

This is a tough one. I'm going to focus on the first case, but I think they are all probably the same underlying issue. The first case is:

<WrapperB client:load>
  <WrapperA/>
</WrapperB>

It winds up looking like this:

solid nested slots

The sharedContext.registry object that Solid uses internally has the wrong DOM nodes in this case. I think it's because they have the same data-hk values.

I suspect the way that we render and call ssr a couple of times and then renderToString, all separately, causes the keys to overlap. I think Solid probably expects only one call to happen.

matthewp avatar Aug 08 '22 20:08 matthewp

Ok, so the underlying issue is that each renderToString creates a new non-shared context.

You can pass a renderId to renderToString which is used to generate the data-hk. I think if we give a distinct renderId for each call it will result the issue.

matthewp avatar Aug 08 '22 21:08 matthewp

Ok, passing a renderId through does work. I need to write tests and a proper PR, but I think I have a fix.

matthewp avatar Aug 08 '22 21:08 matthewp

Nice job! So Astro wasn't passing a unique id to the renderToString? I remember Ryan saying that this is something that he needs to do to support partial hydration properly. And it was a while ago. Guessing the adapter wasn't updated?

thetarnav avatar Aug 08 '22 21:08 thetarnav

Yeah that's right, I have a PR that fixes it, #4215

matthewp avatar Aug 09 '22 15:08 matthewp