WebReflection / lighterhtml

The hyperHTML strength & experience without its complexity 🎉
https://medium.com/@WebReflection/lit-html-vs-hyperhtml-vs-lighterhtml-c084abfe1285
ISC License
735 stars 20 forks source link

html.for skips nested components #47

Closed vdzk closed 5 years ago

vdzk commented 5 years ago

Hello! Thanks for creating lighterhtml. I've started using it recently and I don't have experience with hyperHTML. I've stumbled upon an issue with reordering nodes. I tried to solve it with html.for but now html.for is giving me trouble. Here is code demonstrating the issue + codepen.

const {render, html} = lighterhtml;

const list = [
  {id: 1, desc: 'some'},
  {id: 2, desc: 'item'}
];

const Button = (text) => html`
  <button>
    ${text}
  </button>
`

const ListItem = (item) => html.for(item, ':li1')`
  <li>${item.desc}</li>
`
const ListButtonItem = (item) => html`
  <li>${Button(item.desc)}</li>
`
const ListButtonForItem = (item) => html.for(item, ':li2')`
  <li>${Button(item.desc)}</li>
`

const Lists = () => html`
  <ul>
    ${list.map(ListItem)}
  </ul>
  <ul>
    ${list.map(ListButtonItem)}
  </ul>
  <ul>
    ${list.map(ListButtonForItem)}
  </ul>
`

render(document.body, () => Lists());

Expected result: third list looks the same as the second list. Observed result: items of the third list are empty. How can I use nested components inside html.for? Is this a bug, beyond scope of html.for, or am I doing this wrong?

WebReflection commented 5 years ago

Very interesting bug and super useful test case, thank you.

For the time being, I suggest you don't use html.for when you need nested holes. Will try to figure out what's going on ASAP.

WebReflection commented 5 years ago

OK, latest lighterhtml should've fixed this. Please let me know if that's not the case.

vdzk commented 5 years ago

Thanks for such a quick update! The latest lighterhtml no longer skips nested holes in the test case and in my app. However html.for now breaks when I try to reorder nodes. I've prepared another test case to showcase this + codepen.

const {render, html} = lighterhtml

const list1 = [
  {id: 1, desc: 'apple', priority: 1},
  {id: 2, desc: 'banana', priority: 2},
  {id: 3, desc: 'orange', priority: 3},
]

const list2 = [
  {id: 1, desc: 'apple', priority: 1},
  {id: 2, desc: 'banana', priority: 2},
  {id: 3, desc: 'orange', priority: 3},
]

const byPriority = (a, b) => b.priority - a.priority

list1.sort(byPriority)
list2.sort(byPriority)

const increase = (list, item) => () => {
  item.priority += 1
  list.sort(byPriority)
  update()
}

const decrease = (list, item) => () => {
  item.priority -= 1
  list.sort(byPriority)
  update()
}

const Button = (text, onClick) => html`
  <button onclick=${onClick}>
    ${text}
  </button>
`

const ListItem = (item) => html`
  <li>
    ${Button('▲', increase(list1, item))}
    P:${item.priority}
    ${Button('▼', decrease(list1, item))}
    ${item.desc}
  </li>
`

const ListForItem = (item) => html.for(item, ':li')`
  <li>
    ${Button('▲', increase(list2, item))}
    P:${item.priority}
    ${Button('▼', decrease(list2, item))}
    ${item.desc}
  </li>
`

const Lists = () => html`
  <ul>
    ${list1.map(ListItem)}
  </ul>
  <ul>
    ${list2.map(ListForItem)}
  </ul>
`

const update = () => {
  render(document.body, () => Lists())
}

update()

Steps to reproduce: In the second list (rendered with html.for) click on the down arrow of the first (orange) item twice. Expected result: the item (orange) moves down like in the first list. Observed result: the item doesn't move, buttons disappear, console shows "Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.".

WebReflection commented 5 years ago

thanks again, but please file another bug so I can address these two very different issues, thanks!