-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Obscure error message when upgrading to 4.7.1 #8173
Comments
@kpfefferle Are you using @cached
get record() {
return this.store.createRecord('record');
} Maybe some code like that caused the error. |
I’m seeing this in an app at work and haven’t tracked it down yet. Tag is basically isValid/isError etc flags where ref is the tracked property on it (we create individual companion props for each of these to deal with various requirements of both the computed world and the tracked world) |
Incidentally this is why the internals of EmberData don’t use tracked properties, it would pretty much be impossible for us to avoid these sorts of errors if we did 😅 My best guess is that something around property creation/access has started generating a change notification that dirties itself during mutation. |
@alexraputa We're not doing that exactly, but the constructor in the "offending" Component does use import Component from '@glimmer/component';
import { service } from '@ember/service';
import StoreService from '@ember-data/store';
import ComponentModel from 'my-app/models/component-model';
import RelatedModel from 'my-app/models/related-model';
interface MyComponentArgs {
relatedModel: RelatedModel;
}
interface MyComponentSignature {
Args: MyComponentArgs;
}
export default class MyComponent extends Component<MyComponentSignature> {
@service declare store: StoreService;
@tracked declare newModel: Partial<ComponentModel>;
constructor(owner: unknown, args: MyComponentArgs) {
super(owner, args);
this.createNewModel();
}
createNewModel() {
let { relatedModel } = this.args;
this.newModel = this.store.createRecord('component-model', {
relatedModel, // if I remove this, we don't get the error
});
}
} Is this method of creating a new model within a Component no longer supported? Or is this a bug? 🤔 |
Most definitely this error is a bug. |
was hoping to fix this in the next 4.7 patch but I don't have a good test for it. I have some suspicions around what might be triggering it (some of which are sadly unfixable as they are limitations of how tracking works in Ember unless Ember were to directly expose |
Most of the instances of this in our app are around arrays, not createRecord specifically (though it sometimes may be a createRecord where a peekAll is also in use). @kpfefferle @alexraputa does that align with your experience? |
@runspired Ours is not a |
@alexraputa @kpfefferle yeah I'm no longer convinced fully this is a bug in EmberData. The backtracking that's happening here is real and its the pitfall of trying to mutate app-wide state within a component. I think what has changed is that EmberData no longer uses computed properties (which "untrack" changes) for many things, combined with we've eliminated many inefficiencies where we were previously doing setup "async" (not actually) via a second pass of the run-loop. A solution is to make createRecord actually async, it's something I've considered doing before. In fact many of the proposed design directions for the store convert all or most API's to async for these sorts reasons. However both of these things are breaking changes in a much larger sense. |
@runspired I can understand how a component associating a new record with an existing one could reasonably be understood as mutating the related component in a way that invalidates it in a wider context (and in this case, as an argument to the component itself). However, the use case seems to be a common one: the user has clicked a button that opens a form to create a new model related to a given existing model. Do you have a suggestion for how ember-data users should accomplish this goal without triggering this error? Should we preemptively create the new model in the route (even if the user may never click on the button to open the form)? |
@kpfefferle I typically create it as part of the click action to open the modal, or if I prefer to buffer I create it on the click action to submit the modal. In both of these cases you will avoid any backtracking. |
@runspired We literally only have one component in our application that's doing this, so I'll give those approaches a shot to get us back on the latest stable train 👍 |
All this said I am poking at this to see if there's something simple I can do. I don't want to go back to two pass, but maybe I can defer the relationship and record-array notifications with a deprecation that you resolve via a special flag (since we can't possibly detect this ourselves). Along the lines of the sync=>async observer switch over. |
@runspired, this code also cause the deprecation: import { inject as service } from '@ember/service';
import Component from '@glimmer/component';
export default class Checkout extends Component {
@service store;
order;
constructor(owner, args) {
super(owner, args);
this.order = this.findOrder();
}
findOrder() {
let order;
const orders = this.store.peekAll('order');
if (orders.length !== 0) {
order = orders.find((found) => found.isNew);
}
return order ?? this.store.createRecord('order');
}
} |
Yeah. That's where folks using the resource pattern are regularly adding |
One thought, the actual primitive needed here is a tracking transaction. While An example where folks are regularly hitting failures are things like Since EmberData has fairly good control of its tracking updates, I could probably create such a transaction primitive specific to ours to silently dirty props and only notify A rough API for this might be import { transact } from '@ember-data/tracking';
function findOrder(store) {
return transact(() => {
let order;
const orders = store.peekAll('order');
if (orders.length !== 0) {
order = orders.find((found) => found.isNew);
}
return order ?? this.store.createRecord('order');
});
} Though a memoized version might be nicer: import { memoTransact } from '@ember-data/tracking';
const findOrder = memoTransact((store) => {
let order;
const orders = store.peekAll('order');
if (orders.length !== 0) {
order = orders.find((found) => found.isNew);
}
return order ?? this.store.createRecord('order');
}) |
I'm also curious if @developit has given any thought to these sorts of issues for Signals (I'm happy to give a more abstract summary if you can't gather from context above of what I'm asking). I want to bring EmberData to the React/Preact community early next year and I'm sure we'll hit this sort of problem there as well. |
It would be nice to have API like that.
@wycats, maybe @starbeamjs have the similar API? |
@alexraputa @kpfefferle I have a spike of transact working in this PR: #8214 The public API for it will be exactly as I showed above, and I'm shipping it with typescript support 🎉 |
Closing as tracking lib has shipped |
I hit this bug when migrating to ember-data@4.7 as well. I believe our use case is slightly different than what has been previously In We then transition to I've worked around this regression by buffering the creation of "model-a" in I also tried to import I should add that our app uses an in-repo-addon and that @runspired, do you believe our use case is not supported by @ember-data/tracking? |
the way ember/glimmer works, this should not result in this issue if this is in fact true, which suggests to me that this is not true or some other thing also consumes the state. I don't know that I'd use a proxy to buffer the creation, but I wouldn't call |
I agree, this is strange! But when we directly access I am out of ideas here 😊
How would you buffer the creation then? I'd like to add you idea of the implementation to my choices. Thx 🙏 |
Description
When attempting to upgrade to 4.7.1, we have a couple of tests failing with the following error:
The items being referenced don't exist in our app code, so we're not sure what exactly we might be doing to trigger this error. Would you have any ideas what might have changed that we're now infringing on @runspired?
Versions
The text was updated successfully, but these errors were encountered: