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

Custom rendering is incompatible with caching #951

Open
palkan opened this issue Jul 21, 2023 · 1 comment
Open

Custom rendering is incompatible with caching #951

palkan opened this issue Jul 21, 2023 · 1 comment

Comments

@palkan
Copy link

palkan commented Jul 21, 2023

Turbo version: 7.3.0

I tried using Turbo Drive with morphdom and encountered an issue with restoration visits ("Back" button).

Given the basic morphdom setup as per handbook:

import morphdom from "morphdom"

addEventListener("turbo:before-render", (event) => {
  event.detail.render = (currentElement, newElement) => {
    morphdom(currentElement, newElement)
  }
})

We see that going back always shows an incorrect page (it's lagging one page behind). I did some debugging and nailed down the problem—we call snapshot.clone() on the next tick, and it happens that during the next tick the document.body has been already morphed but still refers to the same DOM element as the one that is connected to the snapshot object; hence, calling clone() actually clones the new, updated DOM, not the previous version (as it should):

await nextEventLoopTick()

This makes using morphdom (or any other custom rendering mechanism that preserves document.body) incompatible with caching.

The workaround I found is to add the same next tick hack to the render function:

addEventListener("turbo:before-render", (event) => {
  event.detail.render = async (currentElement, newElement) => {
    await new Promise((resolve) => setTimeout(() => resolve(), 0));

    morphdom(currentElement, newElement)
  }
})

That seems to work but doesn't look like a robust solution.

@s3ththompson
Copy link

s3ththompson commented Jul 29, 2023

I ran into the same issue and added the same (temporary) fix. If this fix is deterministic, it would be nice if it was handled internally. If it's not deterministic, then it seems like the turbo:before-render hook itself needs to be rethought since this behavior is definitely expected...

I also ran into a related issue: usually replacing DOM elements in the render function disconnects and reconnects Stimulus controllers (since the intersection observer registers the old and new DOM nodes changing). I had some Stimulus logic that relied on controllers reconnecting after every render. Once I added morphdom, I had to manually manage this lifecycle since the DOM nodes themselves don't change. This isn't necessarily something that can be handled internally by Turbo, but at the very least, it seems like it might be worth calling out in the documentation.

This is what my render function ended up looking like:

const application = Application.start();

async function nextEventLoopTick() {
  return new Promise((resolve) => setTimeout(() => resolve(), 0));
}

addEventListener("turbo:before-render", (event) => {
  event.detail.render = async (currentElement, newElement) => {
    await nextEventLoopTick();

    application.controllers.map((controller) => controller.disconnect());
    await nextEventLoopTick();

    morphdom(currentElement, newElement);
    await nextEventLoopTick();

    application.controllers.map((controller) => controller.connect());
  };
});

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

No branches or pull requests

2 participants