miguelcobain / ember-composability-tools

ember-composability-tools - Helpers for building a somewhat different kind of components.
MIT License
39 stars 18 forks source link

Some attributes of first Child are undefined #9

Closed BennyAlex closed 7 years ago

BennyAlex commented 7 years ago

@miguelcobain I have a strange bug:

My childComponents have got a 'disabled' attribute, and it's false by default.

Now, try to access the disabled property on the first child, it will be undefinded.

 didInsertParent() {
    this._super(...arguments);

    this.get('childComponents').forEach(function(child) {
      console.log(child.disabled);
    })
  },

will log with 3 child components:

undefined 2 false

miguelcobain commented 7 years ago

Here is a twiddle with your requirements: https://ember-twiddle.com/bd4821f05452b8e17b09e6dba3b5756f?openFiles=components.parent-component.js%2Ctemplates.components.child-component.hbs

Let me know if I missed something.

BennyAlex commented 7 years ago

@miguelcobain thank you. I didnt knowed that when using

{{yield (hash
  child=(component "child-component" disabled=disabled parentComponent=this)
)}}

and having a default value in the child component:

import Ember from 'ember';
import { ChildMixin } from 'ember-composability-tools';

export default Ember.Component.extend(ChildMixin, {
  tagName: 'li',
  disabled: 'not disabled',
});

and I call

{{#parent-component as |p|}}
  {{p.child}}
{{/parent-component}}

disabled will be undefinded, and not the default value from child component

miguelcobain commented 7 years ago

Right.

When you do that disabled=disabled you're basically overwriting the disabled property to undefined, because disabled isn't defined in the parent context.

BennyAlex commented 7 years ago

I overlooked the right context.

I am really sorry for troubled you 😞

BennyAlex commented 7 years ago

When I try to filter the childs, I get "Assertion Failed: You modified "activeTabs" twice in a single render". What can I do?

  activeChilds: computed('childComponents.@each.disabled', function () {
    if (this.get('childComponents')) {
      return this.get('childComponents').filterBy('disabled', false);
    }
  }),
miguelcobain commented 7 years ago

@BennyAlex it is very common to fall on that trap when using this kind of composability. This happens because the act of rendering a component changed something that was already rendered.

You should try to avoid rendering things in the parent that depend on properties of the children. Keep in mind that I don't mean you can't use properties from the children. You can use them internally in the component freely, just don't use them to render something. With that being said, sometimes you absolutely need to do so. One trick that I frequently use is to render those things after I yield.

In your example, instead of doing

<div>There are {{activeChilds}} active children.</div>
{{yield childrenComponentsHere}}

you could do

{{yield childrenComponentsHere}}
<div>There are {{activeChilds}} active children.</div>

You may face a styling issue then because the parent DOM is after the children DOM. Try to use flex-order or something similar to correct that.

BennyAlex commented 7 years ago

@miguelcobain wow, looks like there are a lot of dangers... Your solution works fine, thanks for that!

miguelcobain commented 7 years ago

This addon wasn't born exactly for this scenario. When components don't render DOM (like in the ember-leaflet case), this isn't a problem. It can be very useful nonetheless.

BennyAlex commented 7 years ago

But there arent any other ways to archive it, or?

miguelcobain commented 7 years ago

Rendering something that depends on children components state has to either be done through services or contextual components, afaik. This is just a tiny abstraction on top of contextual components. That way you don't to manage component registration/unregistration yourself, etc.