Ember.js: [Glimmer 2] "Backtracking re-render" is now an assertion

Created on 29 Jul 2016  ·  63Comments  ·  Source: emberjs/ember.js

Backtracking re-render refers to a scenario where, in the middle of the rendering process, you have modified something that has already been rendered.

For example:

{{foo}} {{foo-bar parent=this}}
// app/components/foo-bar.js

export default Ember.Component.extend({
  init() {
    this._super(...arguments);
    this.get('parent').set('foo', 'bar');
  }  
});

As you can see, by the time the foo-bar component is instantiated and rendered, we have already used the value foo from the parent context to populate the {{foo}} curly. However, in its constructor, it was trying to modify the same value, hence. "backtracking".

This is a pretty extreme example, but it illustrates the problem. Besides init, didInitAttrs, didReceiveAttrs, willInsertElement and willRender also happens synchronously during the rendering process. Additionally, backtracking is often an issue arising from the behavior of two-way bound properties.

This behavior has always been unreliable, but was partially supported with a deprecation (since Ember 1.13):

You modified ApplicationController.foo twice in a single render. This was unreliable in Ember 1.x and will be removed in Ember 3.0

Since 1.13, Ember supported this by immediately doing a second re-render when backtracking was detected (and then repeating until the system stabilizes). This strategy itself could be a source of performance problems. In extreme cases, this could cause an infinite loop.

In Glimmer 2, while the extra re-render is relatively cheap, the extra book-keeping to detect a backtracking set is not. One of the wins of the Glimmer 2 system is that it does not need to eagerly setup observers to track changes. Further, certain optimizations in Glimmer 2 allows the system to skip traversing subtrees when it knows nothing within it has changed.

Together, these factors mean that we can not readily detect these backtracking sets (or whether something was "already rendered" or not) without doing a large amount of extra book-keeping and intentionally defeating these optimizations.

We already wrote the code to support this, but due to the already unreliable nature of the feature, and the (very significant) book-keeping costs, we are hesitant to automatically enable them for everyone without knowing whether it is still needed.

As a compromise, we currently only perform the detection in development mode and turned the deprecation message into a development-mode assertion (hard error). In production mode, the detection code is stripped and backtracking will not work.

We have kept the facility to support this feature (without the assertion) in the codebase behind a second feature flag. The code is being tested continuously on CI, however, is disabled by default until we have enough usage information to determine next steps.

If you believe you have usage patterns that are affected by this, please provide as much detail about your scenario as possible below. It is very possible that there can be alternatives and/or targeted solutions we can use that do not require a wholesale change to the engine. Therefore, it would be helpful if you provide background information and context about your usage instead of just showing us small code snippets from your codebase.

Ember 2.10 Inactive

Most helpful comment

This was not mentioned in the 2.10 blog post and took me by surprise since the deprecation warning previously said it would be supported until 3.0, as was mentioned above.

All 63 comments

FYI, we had some of these warnings in our app. Specifically, we updated some properties of a service in the init of a component, which would trigger something else on the page to render differently.

It is very simple to fix this warning by scheduling the property change on the next run loop. It took me ~an hour to track down and fix all of the warnings in our (fairly large) app. Although this is technically a breaking change, I agree with your assessment even if it caused me some extra work.

@fivetanley improving the error message here sounds good. I know @krisselden and @stefanpenner has a workflow for tracking down these issues, maybe they can help give you some directions on this.

@joukevandermaas run.next() is not a great fix for this error, though I understand if you are overwhelmed with these errors why you would go there. It is best to try to understand why the back flow of data is invalidating things that are already rendered.

Likely if you set props on a service that could be injected onto any component, it increases the chances of that set invalidating something that was already rendered. In general, the pattern should be that set() is only used on internal state during rendering hooks, not tied to input or services and or set() is used on an event, input state should be settled by the time stuff renders.

@joukevandermaas run.next() is not a great fix for this error,

doing so cause performance issues, as glimmer2 in this case is informing "duplicate work is happening, you really don't want this if you want a performant app". Where previously, ember would absorb this, but result in heft performance penalty.

We have some more knowledge sharing work todo here... Ultimately we believe this is a healthy path forward for apps. But we need to make sure everyone has the tools + knowledge available to benefit :)

As a person who follows Ember goings on relatively closely (twitter, here on github, mailing lists, etc) this issue snuck on on me so I would suspect that this might catch others by surprise if it lands as part of Ember 2.10, particularly because the deprecation warning associated with it specifically states the behavior will be supported until 3.0. I don't believe I've seen it socialized anywhere that this behavior will not work in Glimmer 2 (although I may have simply missed it).

suspect that this might catch others by surprise if it lands as part of Ember 2.10, particularly because the deprecation warning associated with it specifically states the behavior will be supported until 3.0. I don't believe I've seen it socialized anywhere that this behavior will not work in Glimmer 2 (although I may have simply missed it).

yup, we need to improve some messaging / details here.

I see this made it into the 2.10 release. Will this be mentioned in the 2.10 release blog post?

This was not mentioned in the 2.10 blog post and took me by surprise since the deprecation warning previously said it would be supported until 3.0, as was mentioned above.

I have a usage pattern that's affected by this. I'm sure the problem must be my usage pattern, and not this particular change, but I would love some input on what a good alternate usage pattern would be!

Basically, I have a page that shows a filterable set of data, and to achieve this, I'm using an Ember computed value to filter the data based on the value of several query-params on the page. However, to prevent invalid inputs (eg not letters or numbers) from being added to query-params from user input, I have the following pattern:

 filteredModel: Ember.computed('model', /*list of individual query params*/, function(){
    let model = this.get('model').filterBy('pdf.pdf_path.url'); //removes all records that don't have a pdf uploaded
    this.get('queryParams').forEach((filter)=> { // for each possible filter
      if ((this.get(filter).length > 0)) { //if the filter has content...
        //guardian pattern to prevent invalid inputs
        let valid = new RegExp('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$');
        while (this.get(filter).length > 0 && !valid.test(this.get(filter))){
          this.set(filter, this.get(filter).slice(0,-1));
        }
        //block of code where the model gets filtered
        //...
        //...
    });
    return model;
  }),

So basically, when I computed what the filtered model should look like, if any of the filter values have invalid characters in them, I strip off the last character until they become valid. Does anyone have a suggestion of a cleaner way to do validity-checking on these inputs?

This caught us by surprise as well - particularly because we didn't see any warning messages when the app was running with 2.9. When we upgrade to 2.10 the application will not load and references this error. Has anyone else seen this behavior?

@revanar I could be totally off here, but it looks like your CP is actually two different things: one CP to return the filtered model and one to update the filters. I'd move the updating the filters part into an observable. Or, if this is a component and you're passing the query params into the component, I'd move the logic into the didReceiveAttrs hook. From my experience running into the "backtracking rerender" error, I think moving the set operation out of the CP should make the error go away.

We're having a tough time with this as well. I've fixed a lot of the issues, but one instance we're seeing of this deprecation error is puzzling.

Assertion Failed: You modified transitioningIn twice on <app@component:link-to::ember1159> in a single render.

It looks like failure is happening because an ember internal property is being updated more than once. Unfortunately it reproduces during our selenium tests so it's difficult to debug (the selenium driver prevents dev tools from working while the test is running). I traced at least one instance of the problem to a controller.transitionToRoute call made at the end of our sign-in process, but it seems to happen in several different scenarios.

I'm not sure how to proceed with troubleshooting this.

@chancancode mentioned a feature flag for disabling this deprecation error, but I don't see any info about it at https://github.com/emberjs/ember.js/blob/master/FEATURES.md. Does anyone know what the flag is?

For our ember 2.10 migration too, this is the main pain point. We have been fixing a lot of these issues as well. It seems that there isn't any single/clear cut strategy to fix these errors. We have tried following approaches, depending on the use case

  1. Wrapping code in Ember.run.next
  2. Moving any setter code from computed properties into lifecycle hooks, or into event handlers, wherever possible.
  3. Trying different combination of lifecycle hooks for components

We have been having alot of difficulty with this as well. Over the past few years we've accumulated a fair amount of drop downs which automatically select the first element of a certain type from the ember data store cache. This causes a re-render since some parts of the page are driven by the dropdown selection. I'm not quite sure what to do since I don't want to repeat the same code to fill and select the first item of the list on each page the dropdowns are used on.

@scottmessinger Thanks for the feedback. Using a component ended up working out fairly well. I've managed to rid myself of the backtracking error, and I think my code is a fair bit cleaner for it.

Kris Selden has a useful tip for debugging these:

screen shot 2016-12-12 at 13 10 44

I've outlined the steps in more details here: https://github.com/GavinJoyce/backtracking/pull/1#issuecomment-266427152

I'm working on improving the backtracking assertion message. I've cut a 2.10.2-with-improved-backtracking-assertion build which includes these better messages:

Before:

You modified message.message_goal.description twice on <(subclass of Ember.Model):ember3486> in a single render. This was unreliable and slow in Ember 1.x and is no longer supported. See https://github.com/emberjs/ember.js/issues/13948 for more details.

After:

You modified message.message_goal.description twice on <(subclass of Ember.Model):ember3486> in a single render. It was rendered in component:message-edit-expanding-container-component and modified in component:rules/predicate-date-value-component. This was unreliable and slow in Ember 1.x and is no longer supported. See #13948 for more details.

I've a few more things to do before it's ready, but it would be really useful if a few people tried this out in their app. /cc @fivetanley, @bryanhickerson, @revanar, @phammers, @scottmessinger, @tharrington1, @manimis902, @jakesjews, @elwayman02. Please let me know if you see any non-ideal assertion messages in your app.

To try it out, update your bower.json to include the ember dependency as follows:

{
  "name": "backtracking",
  "dependencies": {
    "ember": "intercom/ember#2.10.2-with-improved-backtracking-assertion",
    "ember-cli-shims": "0.1.3"
  },
  "resolutions": {
    "ember": "2.10.2-with-improved-backtracking-assertion"
  }
}

You can see an example application running this build here: https://github.com/GavinJoyce/backtracking/pull/10

I've just cut a 1.11.0-canary build

Is this a typo?

@rwjblue thanks, this was a typo. Updated

@GavinJoyce thanks for taking this on! I tried this out in our app and it is definitely _more_ helpful than the previous message. Sadly for us it still doesn't make it super easy to remediate this problem because the property it notes as being modified is not obviously modified in either location indicated by the error message. I'm not exactly sure why that is but we hope to dig into it more next year.

@Dhaulagiri if you're interested, I'd be happy to organise a screenshare sometime and dive into your exact problem. I'm keen for the error messages to be as helpful as possible and it would be useful to consider your case (and I'll likely be able to help identify the root cause).

FWIW I've found another issue related to this: https://github.com/alexspeller/ember-cli-active-link-wrapper/issues/25

Also tried the branch by @GavinJoyce when trying to find a bug in an ongoing effort to bring a tabs control to ember-paper (referenced PR above). Unfortunately it seems that in my case I also get references to components that does not appear to be involved.

@bjornharrtell do you have a ember-paper branch that I can try?

^ We took a little time to dive into ember-paper issues (https://github.com/miguelcobain/ember-paper/pull/590). It seems that:

  • the new error messages were more useful than the current one
  • they weren't perfect as they didn't handle yielded content well. (the component which contained the {{yield}} was reported as the source which was useful, but not as useful as it perhaps could be)

It may or may not be a bug with the addon, but I encountered this error in https://github.com/DockYard/ember-one-way-controls/issues/136

I've found an issue.
I'm using a mixin which has an entry points to update the same property for controller to which it was mixed. These are definitely different properties cause they simply belongs to different controllers and the assertion fails and blocking js.
I have tested mixin by unwrapping it into a few controllers and made a transition between routes to reproduce - it wasn't reproduced.

For now I'm trying to get rid of mixins so I'll simply kill it and will create a work around.

I believe I have a valid use case where we are seeing this re-rendering issue. In our app, we have a button that contains some state (ok, validating, warning, error). It's a component that shows current state, validates when certain things happen, and based on the result of validation or activation, displays different things (spinners, different button text, different button class).

We check validation on init() and based on the response of validation, set the appropriate button state. Button class is a computed property that sets the appropriate classes based on button state. Since it happens on init, it seems like this error is being triggered because we start out with status of ok on instantiation, then transition to validating as we are validating the version and final state based on the response. However, the use case itself seems reasonable and therefore the state changes that are happening also seem reasonable.

@tundal45 might you be able to create a twiddle or sample app which demonstrates what you believe is an incorrect error?

@Blackening999 @tundal45 can you reduce your usecase into a twiddle?

@chancancode @Blackening999 I will try to post one here soon. Thanks for a quick response.

@chancancode https://ember-twiddle.com/936d549b5625b0cf4f3c945d0ed04d3b?openFiles=components.button-with-state.js would be the twiddle but I am not seeing the error that I am seeing in the app so might be some other thing that is causing it.

I'm seeing this on several computed properties, one of which is a simple Ember.computed.or. Since none of @manimis902 's suggestions are applicable in this case, what would be an appropriate workaround?

As @tharrington1 mention where is the flag for the feature mentioned by @chancancode ?

@cbou as far as I know that flag doesn't exist

I wasn't entirely sure how to handle these deprecation notices, nor am I sure if my fix was/is valid, but I was doing an AJAX request in the init() component hook which triggered a value change in a different service's property (to track/show if accessing the remote server).

I moved my AJAX request code from the init() component hook to the didRender() component hook and it seems to have resolved my deprecation notice.

@lvl99 I did the same thing.

We are trying hard to upgrade our enterprise project to Ember 2.12 from 2.3. At our project, we have a validation add-on and a separate form components add-on. The validation add-on works on Ember.components to generate validation errors and the form components add-on display the errors generated by the validation add-on via a helper. The add-ons are way too complicated to share source code; hence we created the following twiddle to illustrate the case we are facing.

The given example contains two components (person-detail and address-detail) which are responsible to generate their own validation errors. The validation errors generated by address-detail is cascaded to containing component (person-detail) via action thrown within errors computed property. Errors generated by each component is displayed within an error-displayer component with the help of the helper error-formatter. The code provided is working as expected as you can see.

However; we have a deprecation warning as follows: DEPRECATION: Theerrorproperty ofis anEmber.Bindingconnected tovalidatable.errors.name, butEmber.Bindingis deprecated. Consider using analiascomputed property instead. [deprecation id: ember-metal.binding] See http://emberjs.com/deprecations/v2.x#toc_ember-binding for more details. In order to avoid that; please go into the helper error-formatter and comment out line number 9 and uncomment line number 8 so that we are doing as suggested in the warning explanation.

Now we hit to the infamous Assertion Failed: You modified "error" twice on <Ember.Object:ember338> in a single render. It was rendered in "component:error-displayer" and modified in "component:error-displayer". This was unreliable and slow in Ember 1.x and is no longer supported. See https://github.com/emberjs/ember.js/issues/13948 for more details. We understand why we are getting this error; because action triggering within address-details errors computed property result in re-calculation of errors computed property of person-detail and content that is already rendered is causing this error. Here is what we would like to learn:

  1. Ember.Binding vs Ember.computed.alias is working quite differently for sure in terms of re-calculation (re-rendering) phase. What is the exact difference? Suggesting using the latter as a replacement to the first one seems to be breaking code; at least for our case.
  2. Is triggering an action from within a computed property a problem? If yes; what are possible suggestions to avoid it?
  3. We are considering wrapping action triggering withing a Ember.run.scheduleOnce('afterRender', ...) statement. Is this the right way to go?
  4. Finally; please go back to now breaking code and type something to any field; and surprisingly the components are re-rendered multiple times; we suspect this might be related to a bug.

FWIW, I've seen my components rerendering more frequently than I would expect in some cases. Tracking down the cause of a rerender is quite time consuming because it's often synced with a runloop. I'd love to know if there are shortcuts or debugging tricks in this area.

Is there a way to catch or suppress this assertion on debug builds? We have tracked down and fixed this via refactors as described above in most places, but there are 1 or 2 that are rather stubborn. In one particular case, we are destroying an object, then transitioning. The destroyed object (on our API) sends a pusher notification to unload/destroy several more objects.

In production builds, the re-render is not an issue, since we are just destroying the object, then transitioning away anyways (so the whole route is being torn down). However, the assertion on dev builds is quite frustrating as it requires a refresh to restore the site. I understand why we are getting the error, but its quite a complicated refactor in this case to avoid it, and in any case it doesnt matter since the entire route is being destroyed. Is there a way to catch / suppress / change this to a warning? I tried a try/catch, as well as the route's error handler, but neither picked it up.

@feanor07 sending an action from within get that invalidates the a property that already has been rendered is data backflow, before bindings with a cycle would detect and silently pick the forward direction as the winner, but this comes at a large cost if this flowed through several things and flows back to the source.

Basically you have a cyclic dependency and you need data to flow down, don't render the error before you have run the validation. You can validate in a parent component that yields out the model and errors to render the form inside its block.

@feanor07 Ember.run.scheduleOnce... did not work, but adding just one Ember.run.schedule("afterRender", () => { ... }); to the property set first indicated by the stack trace eliminated many error messages that cascaded after the initial one.

@neilthawani that may hide the error, but it doesn't necessarily fix the problem. The error is meant to indicate that you are setting a value twice when you should probably only be setting it once. By putting one of the set in schedule, you're just delaying the second set so that it happens in a different runloop. You haven't fixed the core problem of rendering twice when you only need to render once, just tricked Ember into not knowing there was an issue because now the renders happen in separate runloops.

@elwayman02 Uh oh. Thanks. Basically missed the point entirely.

Edit: After struggling with it, I inserted a few more afterRenders than I was comfortable with. We have a click handler on one of our data viz components that toggles a tooltip. Setting the isDisplaying flag within an Ember.run.schedule("afterRender", () => { ... }); allowed us to debug the real issue, which was actually a backtracking in the controller invoking both the data viz component and the tooltip component.

tl;dr: I didn't keep it in there (got a Maximum call stack size exceeded error once, too), but using it was helpful for debugging until the real issue was uncovered.

PSA for anyone else just now upgrading: The improved "backtracking re-render" assertion error mentioned by @GavinJoyce above is actually included in Ember 2.11. It's also been suggested that jumping straight to 2.11 might be helpful.

Guys, i need some help: This error shows up when i try to use a model property which "belongsTo" another one. I created a blank project, and it still shows the same thing. What am i doing wrong?

Also, on my back-end, i'm using .Net Core with JSON API .Net Core (https://github.com/Research-Institute/json-api-dotnet-core) - following their instructions.

The UI render is broken at this point, however the data loads and i can see the desired values.

    // Profile Model:
    import DS from 'ember-data';
    export default DS.Model.extend({
        'firstName': DS.attr(),
        'lastName': DS.attr(),
        'applicationUser': DS.attr(),
        'contactProfile': DS.belongsTo('address', {
            async: true
        }),
        'companyProfile': DS.belongsTo('address'),
        'companyMailingAddress': DS.belongsTo('address'),
        "companyPhysicalAddress": DS.belongsTo('address')
    });

    // Address Model:
    import DS from 'ember-data';
    export default DS.Model.extend({
        'address1': DS.attr(),
        'address2': DS.attr(),
        'city': DS.attr(),
        'state': DS.attr(),
        'zipCode': DS.attr(),
        'country': DS.attr(),
        'website': DS.attr(),
        'phoneNumber1': DS.attr(),
        'phoneExtension1': DS.attr(),
        'phoneNumber2': DS.attr(),
        'phoneExtension2': DS.attr(),
        'email': DS.attr(),
    });
    // Adapter settings
    import DS from 'ember-data';

    export default DS.JSONAPIAdapter.extend({
        namespace: 'api/json',
    });
    DS.JSONAPISerializer.reopen({
        keyForAttribute(key) {
            return key;
        },
        keyForRelationship(key) {
            return key;
        }
    });
    // Route
    import Ember from 'ember';

    export default Ember.Route.extend({
        model() {
            return Ember.RSVP.hash({
                profile: this.store.findRecord('profile', 1)
            });
        }
    });

    // Template
   {{model.profile.contactProfile.address1}}

and the error I get:
Assertion Failed: You modified "model.profile.contactProfile" twice on @model:profile::ember543:1> in a single render. It was rendered in "template:fuels-ember/internal/profile/template.hbs" and modified in "template:fuels-ember/internal/profile/template.hbs". This was unreliable and slow in Ember 1.x and is no longer supported. See https://github.com/emberjs/ember.js/issues/13948 for more details.

PS: I have tried using Ember.computed method to get the property, and that seems to work. Is this required?

Update: I have also discovered that the data loads just fine inside of an {{#each}} helper, but not directly on the template.

@lbarsukov
Maybe it's related to https://github.com/emberjs/data/issues/5023 where a backtracking re-render is caused by relationships in your jsonapi response having a links property set.

This was the issue for me, which started after Ember Data 2.13.2. Try using ember-data: 2.13.2 to see if this solves your problem.

@daniel-de-wit Thumbs up for the master here - this actually works. It now does what I need it to do an I am happy with it.

@lbarsukov @daniel-de-wit We've released a new version of ember data that fixes this issue.

@lbarsukov I think this has to do with your defined relationships. There's a good chance one ( or some ) of the belongsTo should be a hasMany.

Say you have two models, Question and Answer. If you return 10 answers to the question, but each question serializer refers to its answer, you _have_ to define the relationship properly.

// Question Model:
    export default DS.Model.extend({
        'answers': DS.hasMany('answers'), // if you never reference question.answers you can omit this
        ...
    });

// Answer Model:
    export default DS.Model.extend({
        'question': DS.belongsTo('question'),
        ...
    });

When the data comes across defining multiple question/answer pairs, expecting a 1-1 relationship where there isn't, it infers the question was modified in the middle of the render.

From the initial post, some hooks were mentioned:

This is a pretty extreme example, but it illustrates the problem. Besides init, didInitAttrs, didReceiveAttrs, willInsertElement and willRender also happens synchronously during the rendering process. Additionally, backtracking is often an issue arising from the behavior of two-way bound properties.

Why are didInsertElement and didRender hooks working like a charm but the other hooks fail with twice render error?

@BenjaminHorn @Blackening999 @Dhaulagiri @DingoEatingFuzz @Gaurav0 @GavinJoyce @Redsandro @TRMW @Turbo87 @aklkv @alidcastano @backspace @bdiz @bgentry @bjornharrtell @bryanhickerson @buschtoens @caseklim @cbou @chancancode @danaoira @daniel-de-wit @fivetanley @fotinakis @gabrielgrant @ghost @jakesjews @janmisek @joukevandermaas is this still an issue, perhaps we should close, what do you think?

I fixed any instances of the error in my app.

Anytime I’ve encountered this I’ve been able to rearrange things so that it wouldn’t happen anymore, so I think it’s okay to close.

Yes, please close

end of an era 😬

👍

Sorry to bring up an old issue.

Say for example I am rendering

{{this.myBool}}

and the error is Error: Assertion Failed: You modified "myBool" twice

I can fix the error by changing it to:

{{if this.myBool true false}}

Similarly, if a class name binding is causing the problem

I can change:

classNameBindings: ['myBool']

to

classNameBindings: ['myBool:yes:no']

If I can silence the warning like this, why can't Ember handle it for me?

Here is the demo code I was using:

https://ember-twiddle.com/db7f6e382bd0b1de91447881eebb62a5?openFiles=templates.components.my-component.hbs%2C

None of those things “fixes” the problem. It’s either that you switched to production mode, or there are bugs in the assertion/detection code. In any case, you need to fix the side that assigns/sets the value, not the side that consumes it.

Ok. Thank you. I understand the backtracking problem outlined in this original post because it has an explicit set

But in my demo twiddle, there is no set as far as I can see.

_Edit_ That came out wrong, I mean to say there is no set coming from a _subsequent_ part of the template like there is in the example

Hm, I'm not getting the error in the twiddle, what is the sequence of clicks I should do?

Click open, and then click close. But the close button of the yielded child component, not the parent.

I see, the problem is that during teardown, focusOut is called on the component which calls set on an already rendered property. I'm not 100% sure how the component lose focus and the timing semantics. I'm pretty sure that the variations you tried just confuses the tracking system and mask the underlying issue though. Let's track this in a new issue? I'm not sure if it's a valid issue, but let's do the investigation there.

Was this page helpful?
0 / 5 - 0 ratings