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

Astro components don't work if passed as children to hydrated svelte components #9037

Closed
1 task
JulioGrajales opened this issue Aug 4, 2024 · 9 comments · Fixed by #9519
Closed
1 task
Labels
good first issue Good for newcomers help wanted Issues looking for someone to run with them! improve documentation Enhance existing documentation (e.g. add an example, improve description)

Comments

@JulioGrajales
Copy link

JulioGrajales commented Aug 4, 2024

Astro Info

Astro                    v4.13.1
Node                     v22.4.1
System                   Windows (x64)
Package Manager          pnpm
Output                   static
Adapter                  none
Integrations             @astrojs/tailwind
                         @astrojs/svelte
                         @astrojs/mdx
                         @astrojs/sitemap

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

If you pass an astro component to a svelte component slot and if you hydrate that svelte component the astro component stops working after 1 click or may not work at all.

svelte-astro-error.mp4

What's the expected result?

The astro component should work when passed as a slot to a svelte component

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-iqe45d?file=src%2Fpages%2Findex.astro

Participation

  • I am willing to submit a pull request for this issue.
@bluwy
Copy link
Member

bluwy commented Aug 5, 2024

I'm not sure if this is an Astro issue. The problem is that the script is hoisted as a <script type="module"> and will run once. At the same time, Astro will also try to hydrate the Svelte island, which requires a fetch and hydrate that by the time it's done, the button script has already run.

However, the button script will capture the non-hydrated button first, and then Svelte hydrates and replaces the button instance, causing the reference to be lose.

In this case, I think you need to anticipate that buttons could be loaded later by modifying your script, using MutationObserver perhaps or custom elements. You may also have the button loaded later if the Svelte component can toggle its display, so it's not only related to hydration.

@JulioGrajales
Copy link
Author

I believe it is an issue mainly because it is not specified in the 2 relevant urls of the documentation:

But the contrary, it encourages you to use this defective pattern of passing astro components inside framework components with a <slot /> without any warning of unintended side effects or limitations.

Also, having the developer to take into account this hydration limitation of not passing astro components with client side script to a framework component <slot /> could lead to bad developer experience of having to write extra boilerplate code to avoid this hydration limitation.

Luckily my original svelte component was simple enough to be rewritten into an astro component with imperative javascript and avoid this all together, but that made me lost svelte's simplicity and resulted in more lines of code.

At the very least the documentation could be updated to disencourage about this pattern and/or provide guidance on dealing with this limitation with MutationObserver or something else.

@bluwy
Copy link
Member

bluwy commented Aug 6, 2024

Yeah I think we can clarify in our docs about the limitations of the examples. They currently don't work well when the HTML elements they're querying appears later.

@Princesseuh Princesseuh transferred this issue from withastro/astro Aug 8, 2024
@Princesseuh
Copy link
Member

Transferring this issue to the docs so they can document those limitations. I'm not sure I 100% understand the situation, if you could clarify @bluwy so that docs team can document accurately that'd be amazing!

@bluwy
Copy link
Member

bluwy commented Aug 8, 2024

The docs that needs updating is at https://docs.astro.build/en/guides/client-side-scripts. The page could have a section (under Common script patterns maybe?) that explains that scripts are only run once after the page loads, so querySelectorAll will only query the elements on page load.

To query the elements after hydration or dynamically added to the page, you'd need to use MutationObserver to observe when they're added. Though MutationObserver may be not-so-performant if you observe the entire document, so perhaps you'd want to consider using custom elements instead. (There's already a custom elements section on the page that relates to this a bit)

@sarah11918
Copy link
Member

Thanks for this report! In docs' hands now! 🫡

@sarah11918 sarah11918 added improve documentation Enhance existing documentation (e.g. add an example, improve description) help wanted Issues looking for someone to run with them! labels Aug 8, 2024
@delucis
Copy link
Member

delucis commented Aug 8, 2024

So if I understand correctly the specific interaction here is:

  1. A <script> tag in an Astro component uses some global queries on the DOM (e.g. document.querySelectorAll())
  2. A framework component renders additional DOM on hydration (i.e. adding elements not already rendered during server render) or in this specific example re-renders the Astro component DOM it was passed as a child
  3. The <script> logic does not apply to the framework-rendered elements, because it ran before the framework hydrated and rendered them

I’ll admit it feels like a bit of an edge case and the correct fix is probably one of:

  • use a custom element, which are explicitly designed to add logic whenever an instance is created
  • avoid mixing <script> and framework logic

Using MutationObserver etc. works too but feels kind of like a workaround to not using a more appropriate architectural pattern.

Bit tricky to document 🤔 I guess technically the advice would be something like:

Elements rendered by a UI framework may not be available yet when a <script> tag executes. If your script also needs to handle UI framework components, using a custom element is recommended.

@bluwy
Copy link
Member

bluwy commented Aug 9, 2024

Yeah exactly. I agree that custom elements is probably the most correct way and maybe we can recommend that directly instead. The suggested advice lgtm 👍

I'm not quite sure where we'll put it now though since it's a short sentence and might not worth its own section, but simply a info box? Maybe at https://docs.astro.build/en/guides/client-side-scripts/#handle-onclick-and-other-events but it also has an existing info box.

@sarah11918
Copy link
Member

I think I'd suggest adding a new subheading to the final section "Common Script Patterns", something like:

### Combining scripts and UI Frameworks

Elements rendered by a UI framework may not be available yet when a `<script>` tag executes. If your script also needs to handle [UI framework components](/en/guides/framework-components/), using a custom element is recommended.

Would happily accept a PR for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers help wanted Issues looking for someone to run with them! improve documentation Enhance existing documentation (e.g. add an example, improve description)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants