Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct way to propagate changes from one LitElement to a child LitElement?

I have a LitElement that represents a file upload for multiple files. This uses a sub-component that represents each file.

I'm struggling to find examples of the best practice for propagating changes into the sub component using LitElements as it appears to be very different from Polymer 3

Here's a cut down example of what I'm trying:

import './uploadFile.js';
class Upload extends LitElement {
  ...
  static get properties() { return { files: Object } }
  _render({files}) {
    return html`
      <input type="file" multiple onchange="...">
      ${this.renderFiles(files)}`
  }
  renderFiles(files) {
    const filesTemplate = [];                                                                                        
    for (var i = 0; i < files.length; i++) {                                                                         
      filesTemplate.push(html`
        <upload-file file="${files[i]}"></upload-file>
       `);                                
    }                                                                                                                
    return filesTemplate;                                                                                            
  }
}

When I update the status of a file the upload component re-renders but the upload-file component does not.

What am I doing wrong here? There aren't may examples of LitElement usage out there.

TIA

like image 418
DaveB Avatar asked Oct 27 '25 09:10

DaveB


1 Answers

Best practice is "properties down, events up"; meaning that parent elements should share data with children by binding properties to them, and child elements should share data with parents by raising an event with relevant data in the detail of the event.

I can't comment on what you're doing wrong as I can't see how you're updating the status of the files, or your implementation of the child element.

One thing to be aware of is that because of dirty checking, lit-element can only observe changes to the top-level properties that you've listed in the properties getter, and not their sub-properties.

Something like

this.myObj = Object.assign({}, this.myObj, {thing: 'stuff'});

will trigger changes to an object and its sub-properties to render, while

this.myObj.thing='stuff';

will not.

To get sub-property changes to trigger a re-render, you would need to either request one with requestRender() or clone the whole object.

Here is some sample code showing a basic "properties down, events up" model:

Warning: lit-element is still pre-release and syntax will change.

parent-element.js

import { LitElement, html} from '@polymer/lit-element';
import './child-element.js';

class ParentElement extends LitElement {
  static get properties(){
    return {
      myArray: Array
    };
  }
  constructor(){
    super();
    this.myArray = [ 
      { val: 0, done: false },
      { val: 1, done: false },
      { val: 2, done: false },
      { val: 3, done: false }
    ];
  }
  _render({myArray}){
    return html`
      ${myArray.map((i, index) => { 
        return html`
          <child-element 
            on-did-thing="${(e) => this.childDidThing(index, i.val)}" 
            val="${i.val}"
            done="${i.done}">
          </child-element>
      `})}
    `;
  }

  childDidThing(index, val){
    this.myArray[index].done=true;
    /**
     * Mutating a complex property (i.e changing one of its items or
     * sub-properties) does not trigger a re-render, so we must
     * request one:
     */
    this.requestRender();

    /**
     * Alternative way to update a complex property and make
     * sure lit-element observes the change is to make sure you 
     * never mutate (change sub-properties of) arrays and objects.
     * Instead, rewrite the whole property using Object.assign.
     * 
     * For an array, this could be (using ES6 object syntax):
     * 
     * this.myArray = 
     * Object.assign([], [...this.myArray], { 
     *   [index]: { val: val, done: true }
     * });
     * 
    */
  }
}
customElements.define('parent-element', ParentElement);

child-element.js

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

class ChildElement extends LitElement {
  static get properties(){
    return {
      val: Number,
      done: Boolean
    };
  }
  _render({val, done}){
    return html`
      <div>
        Value: ${val} Done: ${done} 
        <button on-click="${(e) => this.didThing(e)}">do thing</button>
      </div>
    `;
  }
  didThing(e){
    var event = new CustomEvent('did-thing', { detail: { stuff: 'stuff'} });
    this.dispatchEvent(event);
  }
}
customElements.define('child-element', ChildElement);

Hope that helps.

like image 57
Kate Jeffreys Avatar answered Oct 30 '25 15:10

Kate Jeffreys



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!