lit-element icon indicating copy to clipboard operation
lit-element copied to clipboard

Possible new lifecycle hook

Open filipbech opened this issue 6 years ago • 11 comments

Should we add a lifecycle hook for when properties have data - before the first render, so we can do some stuff.

If I want to set a property based on another property, I have to do some not-so-nice settimeout-0 stuff to make it work...

I put it in my demo to show the usecase: https://github.com/filipbech/lit-element-example/blob/master/demo.js#L69-L71

What do you think? wouldn't this be a nice handle, or am I missing something obvious?

filipbech avatar Oct 28 '17 18:10 filipbech

The following works :-)

    async connectedCallback() {
        await super.connectedCallback();
        this.favorite = this.product.isfavorite;
    }

But I am wondering whether we should just render immediately when connected. What is your opinion @justinfagnani ?

kenchris avatar Oct 28 '17 18:10 kenchris

yeah, I saw you did the same thing on invalidate... or maybe the connectedCallback could actually return a promise that resolves? this seems kind of "hacky" and async/await compiles down to crap (huge pile actually) for IE11 support...

filipbech avatar Oct 28 '17 18:10 filipbech

That is exactly what it does today. It returns a promise which resolves :-)

You could also do

connectedCallback() {
  super.connectedCallback().then(_ => {
    this.favorite = this.product.isfavorite;
  });

But as I said, we could probably just let connectedCallback call render directly and not as a microtask.

kenchris avatar Oct 28 '17 18:10 kenchris

super.connectedCallback currently returns undefined... do you have something locally or on another branch? https://github.com/kenchris/lit-element/blob/master/src/lit-element.ts#L87 - no return

filipbech avatar Oct 28 '17 18:10 filipbech

Ah it uses to return the result of this.invalidate(). We should fix that.

kenchris avatar Oct 28 '17 18:10 kenchris

I personally wouldn't return anything from connectedCallback() that's changing the signature, and subclasses might not forward the return value...

edit: "wouldn't" not "would" :)

justinfagnani avatar Oct 28 '17 18:10 justinfagnani

that is a valid point... what would you suggest? lifecycle hook or something completely different?

filipbech avatar Oct 28 '17 18:10 filipbech

@justinfagnani so any downsides to forcing a render in connectedCallback? (ie. no microtask)

kenchris avatar Oct 28 '17 19:10 kenchris

If I want to set a property based on another property

There's a few ways to do this:

  1. Define a getter for the dependent property, if it's cheap to compute. Then it's always up to date and takes up no additional storage.
  2. Manually implement the setter for the source property, and set the dependant property in it. The base class needs to be able to handle this case.
  3. Implement an observer feature for the base class, where observers are called before render()
  4. Have invalidate call renderCallback() which calls render() in the base class. The subclass can override renderCallback() to do work before or after the base class calls render(), including setting the dependent property before render().

I think 1) is best if possible. In this case I think it highlights a data-dependency questions that seems currently unanswered in your example: Should setting element.favorite also set element.product.favorite? If it's truly just a derived property, then no. If you're trying to sync the two, then maybe yes (though I find such APIs tricky to reason about).

export class ProductItemElement extends LitElement {
  static get properties() {
    return {
      product: {
        type: Object
      },
    };
  }

  get favorite() { return this.product && this.product.isFavorite; }
}

justinfagnani avatar Oct 28 '17 19:10 justinfagnani

@kenchris

any downsides to forcing a render in connectedCallback? (ie. no microtask)

Mainly that all the data from above might not be there yet.

Usually, just having property initializers is enough to kick off rendering. For the rare element that doesn't initialize any properties, calling invalidate() in the constructor works. Calling invalidate() in connectedCallback would be useful if the template depends on tree-state, like this.isConnected, this.parentNode, etc.

justinfagnani avatar Oct 28 '17 19:10 justinfagnani

I was thinking for cases where you might want to do

(Typing on phone)

this.canvas = this.$('myCanvas'); which requires the Dom to exist

kenchris avatar Oct 28 '17 19:10 kenchris