# When to update model before view

**URL:** <https://discuss.emberjs.com/t/when-to-update-model-before-view/19101>\
**Category:** General\
**Created:** [September 3, 2021, 5:24pm UTC](https://discuss.emberjs.com/t/when-to-update-model-before-view/19101 "2021-09-03T17:24:12Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![jcrate](https://avatars.discourse-cdn.com/v4/letter/j/e495f1/32.png) [@jcrate](https://discuss.emberjs.com/u/jcrate)\
**Post date:** [September 3, 2021, 5:24pm UTC](https://discuss.emberjs.com/t/when-to-update-model-before-view/19101/1 "2021-09-03T17:24:12Z")

</div>

I have a component I’m using to display a list of related records for a model. I’m using `didReceiveAttrs` to create a new related record if one doesn’t already exist. This allows the user to always have a new record to enter data into, as well as the list of previous records. This works fine in 3.14, but after updating to 3.28, adding the record causes an error about updating `[]` while rendering. Seems like the intention for `didReceiveAttrs` would be to set up the component _before_ rendering.

For now I’ve figured out how to use nvm so I can continue to build 3.14. I guess I’ll have to get this figured out eventually, I’d appreciate any ideas.

---

<div class="post-metadata">

**Author:** ![dknutsen](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.emberjs.com/dknutsen/32/16471_2.png) [@dknutsen](https://discuss.emberjs.com/u/dknutsen)\
**Post date:** [September 3, 2021, 6:22pm UTC](https://discuss.emberjs.com/t/when-to-update-model-before-view/19101/2 "2021-09-03T18:22:38Z")

</div>

Ideally you’d be rewriting this component as a glimmer component anyway, which means didReceiveAttrs wouldn’t be available anymore.

I don’t know any details about the code but I imagine you could use a “new style” computed property, something like:

```auto
  get recordToEdit() {
    let recordOrNah = this.args.model.<relationship>.findBy('isNew'); // this is just an arbitrary lookup to evaluate the "if one doesn’t already exist" case
    if (recordOrNah) return recordOrNah;
    else return this.store.createRecord(...);
  }

```

And obviously expand that based on your needs. This means a CP with side-effects so you want to be careful how you’re managing that extra state. You may want to add the `cached` decorator to ensure this doesn’t get recalculated too much.

If you really want an analog for didReceiveAttrs you could use the `{{did-update}}` render helper or render modifier but that usually isn’t necessary.

---

<div class="post-metadata">

**Author:** ![jcrate](https://avatars.discourse-cdn.com/v4/letter/j/e495f1/32.png) [@jcrate](https://discuss.emberjs.com/u/jcrate)\
**Post date:** [September 3, 2021, 11:06pm UTC](https://discuss.emberjs.com/t/when-to-update-model-before-view/19101/3 "2021-09-03T23:06:58Z")

</div>

I changed it to a Glimmer component and it works. However, I have the same notice as a deprecation instead of an error.

```auto
DEPRECATION: You attempted to update `[]` on `<(unknown):ember1302>`, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.

DEPRECATION: You attempted to update `notes` on `<run-schedules@model:house::ember1303:1288>`, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.

```

and after creating the note:

```auto
DEPRECATION: You attempted to update `content` on `PromiseManyArray`, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.

```

```auto
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';

export default class NoteListComponent extends Component {
  @service store;

  constructor(owner, args) {
    super(owner, args);
    
    let house = this.args.house;
    console.log(`note-list.js house ${house.get('orgId')} has ${house.get('notes.length')} shoot notes`);
    let newNote = house.get('notes').findBy('isNew', true);
    console.log(`note-list.js found newNote ${newNote}`);
    if (!newNote) {
      console.log(`note-list.js creating new note`);
      let ignoredBlankNote = this.store.createRecord('note', { house: house });
      console.log(`note-list.js created new note`);
    }
    
    this.notes = house.get('notes');
  }
}

```

If I use a computed property, I get this error when inserting the new record:

```auto
Uncaught Error: Assertion Failed: You attempted to update `[]` on `<(unknown):ember1302>`, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.

```

For context, the NoteList component is called when a modal is displayed which displays the list of notes. The `note-list.hbs` file just delegates the drawing of each note to another component.

```auto
<div class='list-group'>
	{{#each this.notes as |note|}}
		{{note note=note}}
	{{/each}}
</div>

```

I guess this is probably related to interaction with the data store but it seems like there should be a good way for a component to compute the data to be displayed. I can see about computing this data in the parent component but it would make a lot more sense to have this functionality encapsulated here.

---

<div class="post-metadata">

**Author:** ![chriskrycho](https://sea1.discourse-cdn.com/flex019/user_avatar/discuss.emberjs.com/chriskrycho/32/17763_2.png) [@chriskrycho](https://discuss.emberjs.com/u/chriskrycho)\
**Post date:** [September 4, 2021, 12:57am UTC](https://discuss.emberjs.com/t/when-to-update-model-before-view/19101/4 "2021-09-04T00:57:02Z")

</div>

Doing this kind of setting-of-tracked-data _based on other tracked data_ is always going to throw that error, for precisely the reason the error message tells you: you can very easily trigger infinite re-rendering that way. The rendering system _enforces_ that you cannot set reactive (tracked) data you have already read from while rendering as a result.

However, in these cases, if you _have_ to (always a question worth asking!) you can sometimes use a combination of _untracked_ local state with smart uses of getters to accomplish the thing you need to: you cache and supply a newly created object in untracked state if it does’t exist. That’s a “fine” approach for e.g. form draft state and similar problems. Here, that approach won’t work without some further tweaking, because when you call `this.store.createRecord('note', { house: house });`, you _are_ in fact trying to update tracked state you’ve already read in the same computation: the list of `Note`s in the `store`!

The problem here, which the rendering layer is telling you, is not that you are _computing_ the data to be displayed (that would be fine!), it’s that your computation of data to be displayed is also _creating_ data!

What I would suggest here is building the list of notes in a single place—the route, if it’s part of the model, or a parent component, if it’s in response to an action, or similar.

While I understand the desire to sort of “encapsulate” this concern in the component here, the requirement that you _actually_ do one-way data flow will often make that more complicated—but in my experience, putting all the data updates together in a single place is much better long-term: it makes it very much easier to understand the data flow throughout the app.[[1]](#note-1)

The other thing is, you’re _not_ actually encapsulating this particular data concern by putting it in this component as long as you’re creating a model in the data store. The data store is inherently and inescapably a global concern!

One alternative approach this suggests is to have a piece of data here which is a “draft” of the state you want to create, and which you only turn into a value in the `store` once the user persists it. That _does_ allow you to keep the concern local and encapsulated, precisely because it doesn’t couple this to the data store.

In that approach, your component might look something like this:

```js
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';

class NoteDraft {
  house; // can also be tracked if need be
  @tracked content; // This should be like the field in your note model!
}

export default class NoteListComponent extends Component {
  @service store;

  #draft;
  #prevHouse;

  get draft() {
    if (this.args.house !== this.#prevHouse) {
      this.#draft = new NoteDraft({ house: this.args.house });
    }

    return this.#draft;
  }

  get notes() {
    const houseNotes = this.args.house.get('notes');
    return houseNotes.some((note) => note.isNew)
        ? houseNotes
        : houseNotes.concat(this.draft);
  }

  @action saveDraft() {
    const note = this.store.createRecord('note', this.#draft);
    note.save();
  }
}

```

The key bit to understand here is the `draft` getter: we want a new, cached draft any time the house diverges from the previous `house` value. This is _not_ tracked, and acts like a cache: any time we get a new `house`, we get a new `draft`, but it’s otherwise stable. Note that that we _never_ read from tracked state that we’re also assigning to this way! That’s important: it keeps us from having infinite re-rendering cycles. It also will return the same `#draft` no matter how times we ask for it, so we get legitimate caching: we’re never accidentally creating a new draft just by asking for the value of the getter this way. It’s also _lazy_: it will be created on demand when we ask for it in the `notes` getter below it, and not otherwise.

The net here is that we have true one-way data flow, no infinite re-rendering concerns. It’s slightly less clean than just having all of this happen at the level where your app notionally “owns” the list of notes, so that would still be my first recommendation—but it works well and maintains the “encapsulation”.

* * *

## Notes

1. As a bonus: I often think about how I would approach these problems if I were working in a system like [Elm](https://elm-lang.org) or [Redux](https://redux.js.org), where the state is all in a single structure—or even in something like [Shpadoinkle](https://shpadoinkle.org), where local state transformations _have_ to be pure functions, even though they’re local. For a case like this, you would update the shared state store all together in the same action because you would _have_ to. [⏎](#note-1-back)

---

<div class="post-metadata">

**Author:** ![jcrate](https://avatars.discourse-cdn.com/v4/letter/j/e495f1/32.png) [@jcrate](https://discuss.emberjs.com/u/jcrate)\
**Post date:** [September 4, 2021, 2:21am UTC](https://discuss.emberjs.com/t/when-to-update-model-before-view/19101/5 "2021-09-04T02:21:20Z")

</div>

> [@chriskrycho](#):
>
> Doing this kind of setting-of-tracked-data _based on other tracked data_ is always going to throw that error, for precisely the reason the error message tells you: you can very easily trigger infinite re-rendering that way. The rendering system _enforces_ that you cannot set reactive (tracked) data you have already read from while rendering as a result.
> 
> However, in these cases, if you _have_ to (always a question worth asking!) you can sometimes use a combination of _untracked_ local state with smart uses of getters to accomplish the thing you need to, where you cache and supply a newly created object in untracked state if it does’t exist: that’s a “fine” approach for e.g. form draft state and similar problems. Here, that won’t help, because when you call `this.store.createRecord('note', { house: house });` , you _are_ in fact trying to update tracked state you’ve already read in the same computation: the list of `Note` s in the store!

Whether I _have to_ is I guess up to me. As it stands, the app behavior is ideal.

- A new note is only created if the notes list is viewed for a house.
- If the user views notes for a different house, they get a new note for that house.
- If they go back to the previous house, the new note which they may have started entering data still has their data.
- Saving a note is simple, persists it, and pushes it to the back end; no faffing about with temp variables to copy, reset, etc.

As it stands the code is clear as well (at least for me, mostly Rails and mac/ios app development). When the notes are displayed, make sure there’s a new note. (A common Rails idiom is to create a new (not persisted) instance of an ActiveRecord model to render a form for a new record.) If the user never looks at the notes list, nothing related to notes needs to happen.

Managing a NoteDraft class probably won’t be that bad in this case. Since notes can sometimes be edited, I guess the save action can check to see if a the note being saved is a Note or NoteDraft. There would be no need to check for for `note.isNew` in `get notes()` because there could never be a new Note, only persisted Notes and a NoteDraft.

Thanks for pointing me toward a solution. Fortunately this is the only spot in this app with user-editable data!
