question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Render / replaceNode unexpected behavior?

See original GitHub issue

I’m having some issues with render(), followup to #2002 I have an app which is mostly not preact based, but uses a custom made system to generate HTML. We are migrating to preact, so different parts of the app are now created in preact. We currently use preact 8, and use this pattern:

const container = document.getElementById('someId');
let rendered = preact.render(<SomeComponent />, container);

// based on some events an update of the component may be required
preact.render(<SomeComponent />, container, rendered);

// base on another event it must be removed
preact.render("", container, component);
rendered = undefined;

This works like a charm.

Now I’m trying to migrate to Preact X and I’m facing some issues, because render changed. Maybe I do not understand the desired approach for this, but I found some strange effects using render():

https://codepen.io/rhinkamp/pen/abbdBgE The rendered component is not appended to container, but added before the h1.

https://codepen.io/rhinkamp/pen/ExxPNOv A existing div in the HTML is replaced by the component, but it’s position changed to before the h1.

https://codepen.io/rhinkamp/pen/WNNroVQ Using the replaceNode parameter it’s in the correct place, but the nodes look merged, since it’s the component and the id of the original node still present.

https://codepen.io/rhinkamp/pen/eYYJgGV If the replaceNode is a different type (span opposed to div) it is not merged?

https://codepen.io/rhinkamp/pen/gOOPLVJ A button to re-render the component with a different text suddenly drops the “Bar:” text on the first click. On the second click “Bar:” is back, but “allo” is missing.

https://codepen.io/rhinkamp/pen/XWWXpgE Using an empty container node and no replaceNode option does work correct.

https://codepen.io/rhinkamp/pen/OJJMWJL A button to remove the element and a button to add/update. This seems to work, but after removing and adding, clicking the add/update button again will remove all text from the component?

Are these cases correct but am I expecting the wrong thing, or are these bugs?

Is there a desired approach for adding preact component to an existing app? Especially adding it somewhere into the existing DOM tree.

I think I can create container elements to render components in, so I don’t have to use replaceNode, I guess that will fix most if not all issues, but the replaceNode does result in some unexpected behavior?

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:13 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
developitcommented, Jan 10, 2020

Hi all,

Sorry for the radio silence on this one. I just came back to the issue and I’ve got a little guide here to help make sure everyone is on the same page. TLDR is: there is technically a bug here, but it exists in an undocumented feature we had in Preact 8.

Here’s a retrofitted/commented version of @yashrajpchavda’s great CodeSandbox demo with some additional explanations and an example of similar behavior we have today that does actually work in Preact X: https://codesandbox.io/s/preact-differing-root-subtree-render-bug-jywti

I’ll break it down here step-by-step though.

First, we’ll render a DOM tree using Preact:

render((
  <div>
    before
    <a id="link" href="mailto:myself@ab.com">EMail Me</a>
    after
  </div>
), document.body)

Now, say we wanted to specifically update that <a> in-place, without having to re-render the whole tree again. In Preact 8 we diffed against the DOM, so it was possible to “pinpoint” an element in the DOM, and call render() directly on it with a new tree. This might have done some slightly strange things for Components, but for straight Element trees it “just worked” in 8 because we annotated the DOM with lots of stateful properties that were then used to render regardless of the root.

What Does work

In Preact X we can also do this, with the similar constraint that DOM nodes associated with Components will end up with two Virtual DOM trees, which is never desirable. In general, this technique is treading the boundaries of what we can allow and I do worry that it’ll break in the future as we rely more on the Virtual DOM tree and less on the DOM tree for state.

Anyway, here’s the variant that works in both Preact 8 and Preact X - we find the link element in the DOM, and call render() on it with a modified description to apply:

// grab a reference to that <a>:
const link = document.getElementById('link');

// here's what we want to mutate the link to look like:
const updatedLink = <a href="mailto:myself@ab.com">New EMail Me</a>;

// mutate the DOM link in-place to match our VDOM description:
render(updatedLink, link.parentNode, link);

This actually works fine aside from the concerns I mentioned above. It will trigger Preact’s non-hydration initial diffing algorithm, which attempts to reconcile VDOM against DOM (similar to Preact 8’s rendering).

What Doesn’t Work

Now, here’s the case that doesn’t work in Preact X, and that could potentially represent a bug that could manifest itself in other documented use-cases. If we want to do “pinpoint” rendering like this, but the new VDOM tree we pass to render() has a different root element type (eg: <a> vs <span>), Preact X will not remove the previous DOM element we passed and will instead prepend the newly created tree before it. This is because render()'s replaceNode parameter was designed to allow diffing Preact trees within a parent that has other elements Preact shouldn’t touch. In X this is important because it’s fairly common to render multiple VDOM trees into the same parent element using createPortal() - each new tree needs to ignore other trees sharing that parent element.

So here’s the version that fails in X but worked in 8:

// grab a reference to that <a>:
const link = document.getElementById('link');

// we'll try to mutate our link to be a span containing a link:
const updatedTree = (
  <span style="color: #777;">
    This is some more information
    <a href="mailto:myself@ab.com">New EMail Me</a>
  </span>
);

// mutate the DOM link in-place to match our VDOM description:
render(updatedTree, link.parentNode, link);

The output after rendering the above will be this:

  <div>
    before
+   <span style="color: #777;">
+     This is some more information
+     <a href="mailto:myself@ab.com">New EMail Me</a>
+   </span>
    <a id="link" href="mailto:myself@ab.com">EMail Me</a>
    after
  </div>

This is nearly correct, except that the original <a> we passed as replaceNode should have been removed since it was semantically replaced by our whole new tree.

developitcommented, Oct 13, 2019

@richardhinkamp just a note - in your last demo, the target element reference is obtained once and held globally, but the button onclick actually removes that element from the page and replaces it with a Text node. That part is actually intended behavior, and the same as Preact 8. The reason this produces strange results is because target is passed again to the third render() call, but at that point it’s referring to an element that is not present in the DOM tree.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Site broke after update from 9.1 to 9.3.12 | Drupal.org
Im stuck The website encountered an unexpected error. ... twig_render_template() in Drupal\Core\Theme\ThemeManager->render() (line 384 of ...
Read more >
NetSuite Applications Suite - render.TemplateRenderer
Improve Relevance of your Search Results · Troubleshoot Unexpected Search Results ... Customizing Delete Behavior for Records Referenced by Custom Fields.
Read more >
Why does IE give unexpected errors when setting innerHTML
You're seeing that behaviour because innerHTML is read-only for table elements in IE. ... replaceChild(newNode, oldNode); } ... You can modify the behavior....
Read more >
Preact Isn'T Replacing Dom Elements On Rehydration - ADocLib
Inferno is much faster than Preact in rendering, updating and removing elements from the DOM. ... Render / replaceNode unexpected behavior? #2004.
Read more >
Unexpected behavior when DOM structure is modified and re ...
Coding example for the question Unexpected behavior when DOM structure is modified and re-rendered in react-Reactjs.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found