Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hydration always results in duplicate render #3651

Closed
DannyMoerkerke opened this issue Feb 7, 2023 · 4 comments
Closed

Hydration always results in duplicate render #3651

DannyMoerkerke opened this issue Feb 7, 2023 · 4 comments

Comments

@DannyMoerkerke
Copy link

DannyMoerkerke commented Feb 7, 2023

Which package(s) are affected?

SSR (@lit-labs/ssr)

Description

I'm currently experimenting with lit-ssr and I'm running into the problem that a hydrated component is rendered twice. I have a component that needs to be SSR'ed and then hydrated with an array of objects. That data is coming from an api and I notice that the delay that this causes also causes the component to be rendered twice. I forked the example from the lit website and simulated this delay with setTimeout.

Reproduction

https://stackblitz.com/edit/lit-ssr-global-ziy5z1

Workaround

I have not found a workaround.

Is this a regression?

No or unsure. This never worked, or I haven't tried before.

Affected versions

Failing in 3.0.1

Browser/OS/Node environment

Browser: Chrome 109.0.5414.119
OS: MacOS Ventura 13.1
Node version: 18.14.0
NPM version: 9.3.1

@augustjk
Copy link
Member

augustjk commented Feb 7, 2023

The reason for this is that hydration is erroring due to the mismatch of template from the server render that's in the declarative shadow DOM, and what's available client-side at the time of hydration.

At server render, the template is provided a data prop with an array of items to render. Therefore, the template shadowroot contains the result of rendering that data. When hydration happens, that is when the custom element gets upgraded, the element does not have the data prop yet, as in your code, this is added after the custom element is registered. Hydration is erroring at that point.

In order to make sure hydration happens without error, you need to provide the same prop on the client before the custom element is registered so that by the time hydration happens the element has the same data it had when it was server rendered.

See https://stackblitz.com/edit/lit-ssr-global-urdvns?file=src%2Fpages%2Findex.js
I moved the setting of greeter.data to be before the import of the simple-greeter.js definition.

Alternatively, since your example data is serializable, you could provide it on the server as an attribute binding with a JSON string representation of the array, then you wouldn't need to add the data separately on the client script. See https://stackblitz.com/edit/lit-ssr-global-csyetv?file=src%2Fpages%2Findex.js

@DannyMoerkerke
Copy link
Author

Thanks you so much, this is very helpful.
The first example fixes the duplicate render. However, in my use-case I have a setter for the data property.
That still works and the setter is called by hydration but then the error again occurs:

Uncaught (in promise) Error: Unhandled shorter than expected iterable
    at p (experimental-hydrate.js:6:1162)
    at f (experimental-hydrate.js:6:439)
    at s.update (experimental-hydrate-support.js:7:903)
    at HTMLElement.performUpdate (reactive-element.js:6:5538)
    at HTMLElement.scheduleUpdate (reactive-element.js:6:5135)
    at HTMLElement._$Ej (reactive-element.js:6:5034)

See https://stackblitz.com/edit/lit-ssr-global-bxxy8g

@augustjk
Copy link
Member

augustjk commented Feb 8, 2023

Ahhh okay the setter makes it problematic since the setter will only work on the element when it is registered, and the component needs _data property set at the time of hydration. If you can't provide ._data directly due to some work that you need done in the setter that you do not wish to duplicate, the only way to make this work be using defer-hydration as @e111077 suggested in the twitter thread here: https://twitter.com/techytacos/status/1623045639721603073

It'll let you register the custom element without triggering hydration. You can control when that happens by setting the data first and removing the attribute.

Here's your repro modified with that approach.
https://stackblitz.com/edit/lit-ssr-global-ldlz42?file=src%2Fpages%2Findex.js

Notice the { deferHydration: true } option provided to the render() from @lit-labs/ssr. This adds the defer-hydration attribute to the top level component so that it won't hydrate automatically upon custom element registration/upgrade.

We now first import of the component definition and await for its registration, then set the .data property. At this point the element is registered with the class so the setter can do its job. Then removing the defer-hydration attribute will trigger the hydration with the same initial data so it won't cause any errors.

@DannyMoerkerke
Copy link
Author

Thanks again, this works perfectly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants