Custom litelement select not rerendering correctly
Asked Answered
P

2

8

I've created a custom select-component with LitElement:

import { LitElement, html } from 'lit-element';

class CustomSelect extends LitElement {
    static get properties()  {
        return {
            options: { type: Array },
            selected: { type: String },
            onChange: { type: Function }
        };
    }
    constructor() {
        super();
        this.options = [];
    }
    render() {
        return html`
            <select @change="${this.onChange}">
                ${this.options.map(option => html`
                    <option value="${option.value}" ?selected=${this.selected === option.value}>${option.text}</option>
                `)}
            </select>
        `;
    }
    createRenderRoot() {
        return this;
    }
}

customElements.define('custom-select', CustomSelect);

I pass in options, selected and onChange as properties when I create the element. On the first render, everything works fine. All options are rendered and the selected value is reflected in the select. However, if I change selected it doesn't seem to update the selected option. If I inspect the element with dev-tools, the selected attribute is set correctly, but if I start querying the element for its value, it returns the wrong value.

One thing I tried is to add an id attribute to the element via dev-tools after the select has been rendered. If I then change the selected property on CustomSelect, the id attribute persists in the DOM, which says to me that the select is not re-rendered, which is what causing the issue, and why it's working on the first render.

I've tried setting the value and selectedIndex properties on the select-element, but it doesn't seem to affect anything in a meaningful way.

I've logged everywhere (beginning in render() and in the options-map) and all input values are correct.

Publus answered 25/4, 2019 at 21:45 Comment(2)
I'd like to add that in the real application, the select is being rendered in a td in a table. When the select is changed, I fire a state change through Redux that changes the state of some values in the table row, which causes a render of all the rows. Everything else renders correctly. Could it be that since the select is causing the change, its internal state gets stuck somehow?Publus
Adding some extra information: 1. The table is initialized like this. It's sorted on "Status". The selected option is reflected correctly. 2. I change the option like this. This updates the state, and in turn rerenders the rows. The other columns updates correctly, but the select is displaying the incorrect value, even though the option has the correct value selected. The old row is sorted away since we sort on that same column, but it's still displaying the state of that old select on the new row.Publus
T
3

Working example:

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

Fixed using 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>
`)}

https://github.com/Polymer/lit-element/issues/805#issuecomment-530461555

Tarp answered 11/9, 2019 at 17:32 Comment(0)
V
2

It's I think, rendering time and selected property definition on onChange functions timing conflict. So, better to assign a setTimeout in onChange then it's working properly. At my example at below link. I faced the same when I remove setTimeout Also, you don't need to declare onChange as function at properties.

Demo

static get properties()  {
   return {
        options: { type: Array },
        selected: { type: String }
    };
}
constructor() {
    super();
    this.options = [{value:1, text:"ben"},{value:2, text:"sen"},{value:3, text:"oo"},{value:4, text:"biz"},{value:5, text:"siz"},{value:6, text:"onlar"}];
    this.selected = 3
}
render() {
    return html`

        <select id="sel" @change="${this.onChange}">
            ${this.options.map(option => html`
                <option value="${option.value}" ?selected=${this.selected === option.value}>${option.text}</option>
            `)}
        </select>
        <button @click="${this.changeSelected}">Random chage selected option</button>
    `;
}

onChange(x){
 setTimeout(()=>{ 
    this.selected = this.shadowRoot.querySelector('#sel').value
    console.log('Selected -->', this.selected );
   },300)
}
changeSelected(){
  this.selected = (this.options[Math.floor(Math.random() * 6)].value)
  console.log(this.selected)
} 
Vole answered 28/4, 2019 at 9:35 Comment(4)
I'm not sure this is the correct solution in my case, because if I query the incorrect select element and check its value, it has the incorrect value, even though its set as selected. This means that it's simply not a visual bug that can be fixed by a timeout.Publus
@Publus Ok. Could you please create your situation at the above demo link with fork option. And share your case here.Vole
stackblitz.com/edit/litelement-testt-gxjzea here you go. If you change the first row (id 1), you see that that it drops to the third row, which is correct, but then the new first row (id 2) gets the wrong status.Publus
Unfortunately not.Publus

© 2022 - 2024 — McMap. All rights reserved.