Lit-element: Sorting a list of selects

Created on 11 Sep 2019  路  7Comments  路  Source: Polymer/lit-element

Description

Sorting a list of selects by a select has unintended behavior. The selected input keeps it's state even though the list has been re-sorted.

Live Demo

https://stackblitz.com/edit/litelement-testt-jddscf

Steps to Reproduce

Create a list of selects with the selected option depending on the data. Select an option.

Expected Results

The list is sorted with the correct options selected

Actual Results

The input selected keeps it's new state detaching it from the state of the data

Browsers Affected

  • [X] Chrome
  • [ ] Firefox
  • [ ] Edge
  • [ ] Safari 11
  • [ ] Safari 10
  • [ ] IE 11

Versions

  • lit-element: 2.2.1
  • lit-html: 1.1.2

Most helpful comment

The issue here is twofold:

  1. The native selects themselves have internal state that is not reflected back to the data driving the template.
  2. Unless you use repeat() lit-html does not move existing DOM associated with data, but just updates values that have changed, in place.

What happens is that when you change a select value that causes that row to "move", none of the select instances actually move, just the data does. If the new row that moved to the place where the modified select is has the same value as the previous row, the lit-html won't update the select because it doesn't think the data changed.

Both the new live() directive and repeat() will fix this. live() addresses (1), and will cause the selects to be updated if need be based on their current DOM state. repeat() will actually move the select instances so they stay with the data. Both are probably good to do in combination actually.

This could be gone into more detail here: https://lit-html.polymer-project.org/guide/writing-templates#repeating-templates by mentioning that stateful DOM, like native inputs, is a reason to use repeat(), not just performance.

cc @arthurevans should we reopen this as a docs issue?

All 7 comments

Only solutions I found was:

  onSelectChange(id, value) {
    const rows = this.rows;
    rows[id-1][2] = value;
    this.rows = [];
    this.requestUpdate("sortedRows");
    setTimeout(()=> {
      this.rows = rows;
      this.requestUpdate("sortedRows");
    })
  }

Please try the repeat directive

import { repeat } from 'lit-html/directives/repeat';

${repeat(this.sortedRows, e => e, row => html`
    <tr>
    ${row.map((cell, index) => html`
      <td>${this.onRenderCell(cell, index, row)}</td>
    `)}
    </tr>
`)}

You may also want define sortedRows as a field and recompute its value in onSelectChange instead of using a getter to avoid unnecessary computations.

Awesome, that worked perfectly! Thanks!

I just solved a similar select-input issue by using a repeat directive as advised here.

However, I don't really understand why this is necessary, or how I'd know I needed to do this (if not for this post / a related one on stack overflow.) (In contrast, The live directive and its description seemed to really fit this issue as I encountered it / understood it...)

Is there anything I can read/watch to better understand this behavior?
Would it be possible to mention this in the documentation, e.g. binding to input or binding to select specifically?

The issue here is twofold:

  1. The native selects themselves have internal state that is not reflected back to the data driving the template.
  2. Unless you use repeat() lit-html does not move existing DOM associated with data, but just updates values that have changed, in place.

What happens is that when you change a select value that causes that row to "move", none of the select instances actually move, just the data does. If the new row that moved to the place where the modified select is has the same value as the previous row, the lit-html won't update the select because it doesn't think the data changed.

Both the new live() directive and repeat() will fix this. live() addresses (1), and will cause the selects to be updated if need be based on their current DOM state. repeat() will actually move the select instances so they stay with the data. Both are probably good to do in combination actually.

This could be gone into more detail here: https://lit-html.polymer-project.org/guide/writing-templates#repeating-templates by mentioning that stateful DOM, like native inputs, is a reason to use repeat(), not just performance.

cc @arthurevans should we reopen this as a docs issue?

I just ran into this issue with a <select> as well. I had thought of repeat as optional.

Hm... There is already a related issue on lit-html, but it might be worth inserting a note on the LitElement site--perhaps a more generic note about rendering stateful components, which could point to both the live and repeat directives?

Was this page helpful?
0 / 5 - 0 ratings