Skip to content

Delegated events in a Suspense cause resources to refetch on mount (SSR) #2454

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

Open
Mrcavas opened this issue Mar 21, 2025 · 11 comments
Open

Comments

@Mrcavas
Copy link

Mrcavas commented Mar 21, 2025

Describe the bug

When an element with a delegated event handler is present in a Suspense context, firing these events before mount causes all resources to be reran on mount

Your Example Website or App

https://github.com/Mrcavas/solid-delegated-events-bug

Steps to Reproduce the Bug or Issue

  1. Open site
  2. While it's loading, move the mouse around
  3. Notice "fetching......" in client logs

Expected behavior

Firing these events shouldn't refetch all resources

Screenshots or Videos

2025-03-21.23-48-14.mp4

Platform

  • OS: Windows 11
  • Browser: Arc (Chromium)
  • Version: 134.0.6998.89

Additional context

I looked around in sources and found that in solid-js/web, eventHandler sets sharedConfig.done and _$HY.done to true:

if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;

Commenting this line out seems to fix refetches, but I don't know what other consequences this change might cause.

@ryansolid
Copy link
Member

If you interact with part of the page before it hydrates that we can't trust that it will hydrate properly so remainder of the page is client rendered, which can cause resources to refetch as their sources could be impacted. The id we use for resources is stable not based on the value so we have no way of differentiating if the resource would need to be refetched or if it could use the previously serialized values.

@Mrcavas
Copy link
Author

Mrcavas commented Mar 21, 2025

I see, sorry for the confusion then. In my case, I'm using a toast library called somoto which happens to delegateEvents (including mousemove) on import. The import happens right at the app root, which means that all of my Tanstack queries get reran needlessly if one happens to just move their mouse. Is there something you could recommend in this situation?

@mizulu
Copy link

mizulu commented Mar 21, 2025

so you are concerned about the fetch on the client side when you trigger the event

but have you noticed that you are getting double fetch on the server?
even without the event triggering on the client?

@ryansolid
Copy link
Member

I see, sorry for the confusion then. In my case, I'm using a toast library called somoto which happens to delegateEvents (including mousemove) on import. The import happens right at the app root, which means that all of my Tanstack queries get reran needlessly if one happens to just move their mouse. Is there something you could recommend in this situation?

Yeah it's tricky with mouse events. Especially since they are coming from the library and you don't have control over them. Could you mount the toast component after hydration? Like make it visible via a condition set in onMount?

@mizulu it is more that the event has nothing to do with the data fetching. It fetches once on the server and then it is supposed to use that on the client instead of fetching again (ie first time on the client). Currently it fetches in both places.

@mizulu
Copy link

mizulu commented Mar 21, 2025

@ryansolid

it is more that the event has nothing to do with the data fetching. It fetches once on the server and then it is supposed to use that on the client instead of fetching again (ie first time on the client). Currently it fetches in both places.

Yeah, that I understood, but is it expected that the server double fetch ?

I see the 1st message as soon a request is made
and another when the promise is resolved

Image

Image

Image

@mizulu
Copy link

mizulu commented Mar 21, 2025

in regard to the original issue, shouldn't a triggered event, while hydrating cause a refetch only
if the event would have actually propagated to an element inside the suspense?

@Mrcavas
Copy link
Author

Mrcavas commented Mar 22, 2025

Yeah it's tricky with mouse events. Especially since they are coming from the library and you don't have control over them. Could you mount the toast component after hydration? Like make it visible via a condition set in onMount?

I have just tried this approach and realized that it doesn't quite work as I've overlooked other somoto imports. You see, besides the Toaster component being imported at the app root, there are also toast imports all around the app to actually display toasts. I think that would mean I'd need some kind of proxy module that would only import somoto once the app hydrates. I'll try to figure out something like that, but as a last resort, I could just make a patch for this library to only delegate the events after the app mount.

Besides this personal issue with my app, this behavior is very unintuitive, it was a nightmare to figure out why my app sometimes reran all queries and sometimes didn't. Maybe at least add some kind of warning to indicate that these delegated events can cause resources to refetch?


but have you noticed that you are getting double fetch on the server?
even without the event triggering on the client?

Yes, mizulu, I have noticed this and it is also quite interesting why this happens. However, as I use Tanstack Query, requests get deduped automatically, so I'm not really concerned about it as much.

@mizulu
Copy link

mizulu commented Mar 22, 2025

@Mrcavas 👍

if solid had a hook for before hydration onBeforeHydration, then you can remove the event
from the window.document._$DX_DELEGATE

and restore it when hydration is completed

I believe the hydration completion can be detected with this promise
Promise.all(Object.values(_$HY.r))

it's could have been a fairly simple workaround in the interim, but I don't know how easy it is to patch the code into the workflow

perhaps solid can think of better way to handle this
either by providing hooks, or more control over the DX_DELEGATE, or improved detection of when an event actually caused
a change that may require the fetch. right now it seem like a hammer solution.

@Mrcavas
Copy link
Author

Mrcavas commented Mar 23, 2025

Right now what I ended up doing is just:

// entry-client.tsx
import { sharedConfig } from "solid-js"

function hookDoneFor(obj: any) {
  Object.defineProperty(obj, "done", {
    get: () => false,
    set: () => {},
  })
}

hookDoneFor(sharedConfig)
hookDoneFor(window._$HY)

As @mizulu suggested, using Promise.all(Object.values(_$HY.r)), i tried to restore hooked behavior to normal when the page hydrated, however, there was still a small time gap where moving your mouse could refetch some queries, so I ended up not removing this hook at all. I feel like this is very hacky, but it does it's job xD. Still wondering though if there's a better way.

@mizulu
Copy link

mizulu commented Mar 23, 2025

@Mrcavas
for my suggestion to work, we need to find a way to transform the registration of the delegated events
before the client calls it,

_$delegateEvents(["mousemove"]);

then there shouldn't be a gap where the event can be handled.
another option is if you can register your own global event handler
that will run before the solid one, and then catch those events by stopping the immediate propagation

let see if there is more official recommend ways to deal with this.

@kapilpipaliya
Copy link

kapilpipaliya commented May 7, 2025

I am using https://github.com/solid-component/solid-hot-toast for Toast and adding <Toaster /> component is causing issue of not hydrate page after recent packages update. also adding it on mount not worked:

const [isMounted, setIsMounted] = createSignal(false);
  onMount(() => {
      setIsMounted(true);
  });
<Show when={isMounted()}>
  <Toaster />
</Show>

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

4 participants