Ember.js: Rendering <button> in each loop breaks application

Created on 17 Jul 2018  ·  43Comments  ·  Source: emberjs/ember.js

In ember 3.3.0 following template breaks application:

{{#each buttons as |button|}}
  <button>{{button}}</button>
{{/each}}

buttons is a simple array. In console I can see Uncaught Error: unreachable. This error does not happen with other tags (i.e. works fine) and does not happen on ember 3.2.2

Repository with demonstration: https://github.com/GendelfLugansk/ember-rendering-bug

Bug Regression

Most helpful comment

Is this considered a breaking change?

Yes, it is a breaking change and we are working on the fix.

I'm also a little confused by the RFC as to whether this is allowed (and supposed to work), or allowed at build-time (but breaks at run-time).

The current behavior matches what the RFC stated (essentially that a block param named button + <button></button> in the blocks template, would assume that the block param was a yielded closure component and attempt to render it as such), but based on feedback (here and in other issues) we determined that the result is definitely something we consider a breaking change.

The current plan (after discussing with the core team on 2018-07-20) is:

  • Ensure that all block params that "shadow" normal HTML elements inside their blocks, continue to render the HTML element (and do not assume they should "invoke" a yielded component).
  • Issue a deprecation when this scenario is detected (with until: '4.0.0')
  • Ensure the linter is setup for newly generated Ember apps that will lint against using confusing block param names (where the block param _could be_ an HTML element name, and that HTML element is invoked in the blocks template).
  • Backport these fixes to Ember 3.3

All 43 comments

Yeah, confirmed, we've encountered same error after upgrade from 3.2.2 to 3.3.0

I'm somewhat surprised that this broke in 3.3, but I would have expected it to break in 3.4 (due to the angle bracket invocation feature).

I'll try to dig in to confirm if it is related...

Changing to:

{{#each buttons as |text|}}
  <button>{{text}}</button>
{{/each}}

Should resolve the issue (assuming that my guess RE: angle bracket invocation feature is correct).

@rwjblue yes, changing from button to text or btn helps. Thank you for explanations.

Just an informational message: I had the same problem with {{#each model as |img|}}. Changing img to i.e. "foto" resolves this error. Is this breaking change (ember-source 3.2.2 => 3.3.0) documented somewhere?

Just another information: this issue tricks me a lot, the key is: don't use html tag name as the as part of each any helper that yields something.

{{#each people as |p|}} {{!-- don't, because `p` is a html tag name --}}
  {{p.name}}
{{/each}}

{{#each people as |person|}} {{!-- this is fine --}}
  {{person.name}}
{{/each}}

I'm not sure if it will be a breaking change, but I hope not.

@nightire as I can tell, we can use html tag name in as part if that html tag is not used inside block. I also think it affects not just each but any block helper that yields something, including our own components.

Confirm, yielding any block param that will be used as an angle bracket invocation will result in this issue.

See the following issues for some more background information:

This change should not have happened during 3.3, and we will try to figure out why it did (and fix it). However, we _do_ intend for it to land as part of 3.4 (along with support for linting in applications as a general guide).

Should we be expecting for this to be patched in 3.3.1?

Probably, but its still a good idea to refactor away from the patterns that are currently breaking.

The ember-template-lint rule no-shadowed-elements is a great way to enforce this.

I also started getting error: unreachable with this add-on in 3.3...

https://github.com/tedconf/ember-collapsible-panel/issues

Is this considered a breaking change? I'm also a little confused by the RFC as to whether this is allowed (and supposed to work), or allowed at build-time (but breaks at run-time).

Is this considered a breaking change?

Yes, it is a breaking change and we are working on the fix.

I'm also a little confused by the RFC as to whether this is allowed (and supposed to work), or allowed at build-time (but breaks at run-time).

The current behavior matches what the RFC stated (essentially that a block param named button + <button></button> in the blocks template, would assume that the block param was a yielded closure component and attempt to render it as such), but based on feedback (here and in other issues) we determined that the result is definitely something we consider a breaking change.

The current plan (after discussing with the core team on 2018-07-20) is:

  • Ensure that all block params that "shadow" normal HTML elements inside their blocks, continue to render the HTML element (and do not assume they should "invoke" a yielded component).
  • Issue a deprecation when this scenario is detected (with until: '4.0.0')
  • Ensure the linter is setup for newly generated Ember apps that will lint against using confusing block param names (where the block param _could be_ an HTML element name, and that HTML element is invoked in the blocks template).
  • Backport these fixes to Ember 3.3

I have been having this same issue for the last couple of days as well trying to figure out what was going on:

<select>
    {{#each options as |option|}} <!--I see now the issue is I named this "option" a valid html tag -->
        <option value="{{option.id}}">{{option.name}}</option>
    {{/each}}
</select>

Changing to this fixed the problem for as well:

<select>
    {{#each options as |opt|}} <!-- Renamed to "opt" -->
        <option value="{{opt.id}}">{{opt.name}}</option>
    {{/each}}
</select>

Just throwing this out there for more documentation on the issue.

Just throwing this out there for more documentation on the issue.

👍 thanks for that (and sorry for the issue)

@rwjblue I'm not actually ables to find evidence of the no-shadowed-elements template lint rule in https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rules.md

Does it exist somewhere else?

@cafreeman it's not listed there (though it should be), but the rule is here

Ah, can't believe I missed that. Thank you!

The no-shadowed-elements rule isn't very descriptive, ended up here thinking the rule was another issue just with formatting (in vscode with the ember addon there is no difference between formatting issues and errors, I think with the linter too?).

My question is will this have a better error in future? Not everyone will be running the linter and I can imagine coworkers running into this and not knowing what's going on when there error isn't useful.

Ya, I mentioned above that we consider this a bug and totally want to
make the ergonomics much better (e.g. issue a warning that has a helpful
message instead of error).

I'm actually confused why this is ambiguous:

{{#each options as |option|}}
  <option value={{option.value}}>{{option.label}}</option>
{{/each}}

Don't angle-bracket components need to be invoked with a capital letter?

Don't angle-bracket components need to be invoked with a capital letter?

No, see the dynamic invocation section of the RFC for background.

Can anyone reproduce this on the latest stable release of Ember?

@pzuraq yes, I can reproduce this error using 3.8.0

Just to make things incredibly confusing, this can happen (it happened to me)
Running Ember 3.4

{{#each someArray as |item i|}}
  {{#if (gt i 0)}}
    {{fa-icon item.icon}}
  {{/if}}
{{/each}}

Even though I wasn't using an <i>, the {{fa-icon}} component (from ember font awesome) creates one, and this caused the fun Uncaught Error: unreachable error

Also, this isn't caught by ember-template-lint's no-shadowed-elements, making it harder to find and diagnose :(

I can absolutely see how that would be pretty confusing. Internally ember-font-awesome modifies the output template and replaces the {{fa-icon component invocation directly with <i> which is the thing that caused the error you hit.

RE: this not being caught by no-shadowed-elements, I think we could do a bit of work in the ember-font-awesome AST transform to ensure that this specific scenario doesn't exist (basically error or automatically rewrite any block params named i while transpiling).

@Techn1x. @rwjblue Icon name cannot be dynamic in fa-icon (https://github.com/FortAwesome/ember-fontawesome/blob/master/lib/ast-transform.js#L37)

We have dynamic fa-icon all over the place, it does work. Pretty sure what you linked will have had ember/glimmer already turn it into a string. Otherwise there is no chance we would even use the package.

You can also send in an object which has an iconName and prefix set.

Sorry - whether or not dynamic icon names work, that's just an issue with the example I gave, but the point still stands.

The basis of that particular example (using i as a block param, and fa-icon in the inner block) is used in a non-trivial amount of places throughout my codebase - I ended up renaming all block param uses of i to index to be sure

Also worth noting there's two ember font awesome projects going, depending on the FA version you're using. They probably have differences in what they support etc.
https://github.com/martndemus/ember-font-awesome
https://github.com/FortAwesome/ember-fontawesome

Is this issue still being looked at? it appears to be nearly a year since it was reported but I encountered the

Uncaught Error: unreachable

message today with the following code in Ember v3.4.4 (which I believe is a current LTS version).

<select>
    {{#each options as |option|}}
        <option value={{option.id}}>{{option.name}}</option>
    {{/each}}
</select>

P.S. I've since changed the code to:

<select>
    {{#each options as |opt|}}
        <option value={{opt.id}}>{{opt.name}}</option>
    {{/each}}
</select>

which obviously works.

@Caltor latest LTS - 3.8 https://emberjs.com/releases/

@lifeart it also says 3.4 bugfixes until May 2019.

3.4 will only be receiving security fixes at this point, 3.8 will receive bugfixes until 3.12 is promoted to LTS

@rwjblue So LTS actually means "we ignore the bug until the LTS period has elapsed"? Interesting. I would have thought bugs spotted within the LTS period would be fixed. Still I understand it's a community project etc etc and it's no big deal to workaround.

@Caltor https://github.com/emberjs/ember.js/issues/16826#issuecomment-405654150 there is workaround for this case, and workaround pretty clear and it's following official linting rules https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-shadowed-elements.md

@Caltor - I am sorry that we didn't get this fixed for the LTS. 😩

The main issues here are that it has turned out to be gnarly to fix (I have spent a few hours on it), and in nearly all cases updating to avoid the conflict makes your code better and easier to understand which generally makes fixing this particular bug a bit lower priority than others that are actually blocking folks.

@rwjblue No worries! If the linter picks it up then that is good. It is only a result of bad coding anyway.

This issue also happens when a yielded components hash has the same name as helper, and linter doesn't catch that.

<AngleTable as |t|>
  <t.header />
  {{t "unreachable"}}
</AngleTable>

@BobrImperator I'm not sure that problem is something that is possible to catch ahead of time, and it should have always been a failure because local variables have always taken precedence over globals. The reason we can't catch it ahead of time is similar to why you can't lint against something like this in JS:

function foo() {

}

function bar(foo = 'a string') {
  foo(); // Error: foo is not a function
}

While it's reasonable to assume that this is a shadowing mistake on the developer's part, because of JS's dynamic nature, we can't know for sure that foo is _not_ a function when being passed into bar. If we were using a typed language instead, then we may be able to, but unfortunately I don't think we can currently.

I kind of expressed myself badly, I don't really expect this to be caught beforehand.
But if I remember correctly, there's a compile error for action helper when it's overwritten.
I don't remember the exact syntax, but it should be something like this, similar but with action.

<AngleTable as |action|>
  <t.header />
  {{action "unreachable"}}
</AngleTable>

it could be a different case though

FYI just spent a couple hours on debugging til I finally googled and found this issue (was using option and the yielded name) so its still out there on 3.15.

What was weird was that {{log option}} reported the correct value inside the loop, so never occurred to me the name of the variable could be the problem

in ember 3.16.6, it still happen in ios safari.

Was this page helpful?
0 / 5 - 0 ratings