developit / htm

Hyperscript Tagged Markup: JSX alternative using standard tagged templates, with compiler support.
Apache License 2.0
8.67k stars 170 forks source link

inconsistent or duplicate elements rendered when using preact standalone version #159

Closed catdad closed 4 years ago

catdad commented 4 years ago

I created a small repro demo that I set up in two ways:

Code:

//import { html, render } from 'https://cdn.jsdelivr.net/npm/htm@3.0.3/preact/standalone.module.js';
//import { Fragment } from 'https://cdn.jsdelivr.net/npm/preact@10.3.4/dist/preact.module.js';

import { h, render, Fragment } from 'https://cdn.jsdelivr.net/npm/preact@10.3.4/dist/preact.module.js';
import htm from 'https://cdn.jsdelivr.net/npm/htm@3.0.3/dist/htm.module.js';
const html = htm.bind(h);

const elem = document.querySelector("#app");

const Item = (item) => {
    return html`
    <div key=${item.id} data-id=${item.id}>
      ${item.text}
    </div>`;
};

const items = [{id: 1, text: 'one'}, { id: 2, text:'two' }];

const performRender = () => {
  const children = items.map(item => html`<${Item} ...${item} />`);
  render(html`<${Fragment}>${children.reverse()}<//>`, elem);
};

setTimeout(() => {
  items.push({ id: 3, text: 'three'});
  performRender();
}, 1000);

setTimeout(() => {
  items.push({ id: 4, text: 'four'});
  performRender();
}, 2000);

window.onload = performRender;

Fiddle for reference: https://jsfiddle.net/0ukweoz1/2/

Results:

note that also, the render after 1 second does not actually update the DOM, so three is never just added by itself

developit commented 4 years ago

Hi @catdad - this happens because you are using two copies of Preact - Fragment from one, and render() from another. Preact is a singleton and using two copies of it will cause all sorts of strange problems like this, in addition to just downloading preact twice.

Some context - the reason we don't export Fragment from htm/preact/standalone is because it should never be necessary when using HTM, since HTM supports implicit fragments.

However: it looks like the copy of Preact we bundled with htm 3.0.3 has a bug with fragments at the root, or our bundling is breaking something. That's a bug.

Here's your demo without the Fragment usage:

// should work, but seems to have an issue with bundled preact and fragments:
//import { html, render } from 'https://cdn.jsdelivr.net/npm/htm@3.0.3/preact/standalone.module.js';

import { h, render } from 'https://cdn.jsdelivr.net/npm/preact@10.3.4/dist/preact.module.js';
import htm from 'https://cdn.jsdelivr.net/npm/htm@3.0.3/dist/htm.module.js';
const html = htm.bind(h);

const elem = document.querySelector("#app");

const Item = (item) => {
    return html`
    <div key=${item.id} data-id=${item.id}>
      ${item.text}
    </div>`;
};

const items = [{id: 1, text: 'one'}, { id: 2, text:'two' }];

const performRender = () => {
  const children = items.map(item => html`<${Item} ...${item} />`);
  render(html`${children.reverse()}`, elem);
};

setTimeout(() => {
  items.push({ id: 3, text: 'three'});
  performRender();
}, 1000);

setTimeout(() => {
  items.push({ id: 4, text: 'four'});
  performRender();
}, 2000);

window.onload = performRender;
developit commented 4 years ago

This has been fixed! It's available on unpkg and npm as htm 3.0.4.