cibernox / ember-power-select

The extensible select component built for ember.
http://www.ember-power-select.com
Other
540 stars 379 forks source link

Bug: infinite rendering invalidation detected (Octane) #1196

Open gossi opened 5 years ago

gossi commented 5 years ago

Using e-power-select in an MU/Octane project like so:

<PowerSelectMultiple
    @id={{id}}
    @allowClear={{true}}
    @searchField='title'
    @options={{sort-by 'title' this.groups}}
    @selected={{form.model.groups}}
    @onchange={{action (mut form.model.groups)}}
    as |group|>
    {{group.title}}
</PowerSelectMultiple>

causes an:

Uncaught Error: infinite rendering invalidation detected

That said, it only happens when the @selected argument is given (without it works). I tracked it down. These two lines happen to call themselves over and over again:

https://github.com/cibernox/ember-power-select/blob/fd89c411d58733744ce6f7688ddb22594aec28e4/addon/components/power-select.js#L549

and

https://github.com/cibernox/ember-power-select/blob/fd89c411d58733744ce6f7688ddb22594aec28e4/addon/components/power-select.js#L168

I'm still investigating and will update this issues as I come along.

gossi commented 5 years ago

Some more info: form.model is a changeset, while groups is a regular array. Due to two-way binding, when PowerSelect.updateState() is called, the value is also set on the changeset and I guess since these are different objects, changeset invalidates them and the @selected={{form.model.groups}} is re-evaluated, which in turn is calling PowerSelect.selected.set() ... and the loop starts again.

It works with a dummy pojo object instead of form.model which works.

I tried passing different objects into Changeset - with different invalidations. It is definitely somehow related to changeset.

snewcomer commented 5 years ago

@gossi what version of ember-changeset?

gossi commented 5 years ago

With ember-changeset@v2.0.0 the problem is gone. Can happily close this. Thank you so much @snewcomer

gossi commented 5 years ago

I have to reopen, because I've seen this problem without interactions of ember-changeset. I can say, it has a problem with @tracked implementation of sparkles.

Let's take this component:

export default class DemoComponent extends SparklesComponent {
    @tracked optionsA = [];

    @observes('args')
    argObserver() {
        this.optionsA = [];
    }

    @tracked('args')
    get optionsB() { 
        return [];
    }

    void() {

    }
}

and this template:

{{!-- Test Case A: Fails --}}
<PowerSelect
    @options={{this.optionsA}}
    @onchange={{action this.void}}
    as |opt|>
    {{opt}}
</PowerSelect>

{{!-- Test Case B: Fails --}}
<PowerSelect
    @options={{this.optionsB}}
    @onchange={{action this.void}}
    as |opt|>
    {{opt}}
</PowerSelect>

I have a feeling it may be caused, because options as property of power-select can be set (and because of two-way-binding) these changes bubble up 🤔

My idea would be to have a displayedOptions property in power-select, that uses is "compiled" from the options. Means, whenever options or anything filter logic related changes, the displayedOptions are re"compiled" from options. At least to keep them separated!

gossi commented 5 years ago

It looks like glimmer-vm triggers a lot of updateComponent calls (on the component manager). I cannot say when exactly, I was just able to see them and many bubble up from e-p-s. And since I had them tracked, they caused e-p-s to run their updates again. A devicious circle 👿

I think my original idea to stop two-way-binding will help. Since you are migrating things over to octane components, this will support this pattern out of the box.

For now. This is the workaround (along with my intended implementation): https://github.com/gossi/spomoda/blob/82e9127e280afd7ba7bf5fbec66fb03c1e14fe67/client/src/ui/components/skill-form/component.ts#L31-L65

lifeart commented 4 years ago

Also seen this issue using octane + fastboot (in fastboot)

Bouke commented 4 years ago

We're also seeing this when the component starts rendering. We're setting options from ember-data. No ember-changeset involved. If we don't set options, the problem doesn't surface, while search still works.

export default class SelectCustomer extends Component {
    get options(): any {
        return this.store.findAll('customer')
    }
}
<PowerSelect @options={{this.options}} @searchEnabled={{true}} @search={{this.search}}
    @selected={{this.selected}} @onChange={{this.onChange}} as |customer|>
    {{customer.name}}
</PowerSelect>