0

I have an array of strings passed as an argument to a component, inside the component I am using "each" helper to render each string in a text input. I tried the following approach.

MainComponent.hbs
<Component @model={{model}}/>

//Eg: model.list = ["Jack", "Sparrow"];

Component.hbs
<div>
    {{#each this.args.model.list as |name|}}
    <div>           
         <PaperInput @value={{ name }} 
            @placeholder="Enter a Name"
            @onChange={{action (mut name)}}/>        
    </div>
    {{/each}}
</div>

I am running into the error "Uncaught (in promise) Error: Assertion Failed: You can only pass a path to mut". Would really appreciate if anyone can let me know What's going wrong here.

Murali Nepalli
  • 1,588
  • 8
  • 17

2 Answers2

4

The value that are derived from helpers (each in your case) cannot be mutated using mut helper as the helpers usually don't pass down or hold the values to change the original property.

For instance,

It makes sense if we are mutating a value as below where capitalize is a helper:

<button {{action (mut name) (capitalize name)}}>
 Capitalize
</button>

however, below snippet does't fit right as helper returns the value one way!

<button {{action (mut (capitalize name)) name}}>
 Capitalize
</button>

the same thing is going on with the each helper and the looped through value cannot be mutated! This code comment might be useful for further digging.

You can change your snippet to handle the onChange in the backing component class instead:

<div>
    {{#each this.args.model.list as |name index|}}
    <div>           
         <PaperInput @value={{ name }} 
            @placeholder="Enter a Name"
            @onChange={{this.changeName index}}/>        
    </div>
    {{/each}}
</div>
// component.js

changeName(index, nameToBeUpdated) {
 // change the name here...
}

Gokul Kathirvel
  • 1,590
  • 1
  • 10
  • 20
  • Thanks Gokul. how would you update the array in changeName so that it re renders? – Murali Nepalli Feb 04 '20 at 05:02
  • For example, this.args.model.list[index] = name would not work. – Murali Nepalli Feb 04 '20 at 05:38
  • That's how that tracking system works in Ember octane now. This [SO answer](https://stackoverflow.com/questions/57468327/why-wont-my-tracked-array-update-in-ember-octane?answertab=active#tab-top) should help. Also, there is [a section](https://guides.emberjs.com/release/upgrading/current-edition/tracked-properties/#toc_arrays) on property tracking in octane guide which explains an alternative method! – Gokul Kathirvel Feb 04 '20 at 05:54
  • Figured it out. Posted the full implmentation :) – Murali Nepalli Feb 06 '20 at 00:41
1

Figured it out. Posting the full implementation for the benefit of others. I passed down the index value to component's action as suggested in Gokul's answer but ran into another problem. There was no straight forward method to change the array's value. So I used the Mutable Array's replace method do that. That again caused another problem, every time I entered a character in the text input it was chaning the array value and re rendering the list which took the focus out of the input. So in "each" helper I had to set key="@index" which tells the helper to rerender only if there is a array index change, not the value.

Component.js

@action
 updateName( index, name ) {
    this.args.model.list.replace(index, 1, [name]);     
 }
MainComponent.hbs

<Component @model={{model}}/>


Component.hbs

{{#each this.args.model.list key="@index" as |name index|}}
    <div>           
         <PaperInput @value={{ name }} 
            @placeholder="Enter a Name"
            @onChange={{action this.updateName index}}/>        
     </div>
{{/each}}
Murali Nepalli
  • 1,588
  • 8
  • 17