Array is not updating when i used push method

Hello everyone. I ran into the problem that when I push something into an array (array updating), the each loop is not updated and the array remains what it was. I also tried to use @tracked decorator from glimmer/tracked but it didn’t help.

purchases.hbs:

{{page-title "Purchases"}}

<div class="purchases">

<h3 class="a-p">Add Purchase</h3>

<div class="input-field">
    {{input placeholder="Purchase Name" type="text" class="validate" value=pname}}
    {{input placeholder="Purchase Price" type="text" class="validate" value=pprice}}
</div>

<a class="waves-effect waves-light btn" {{on 'click' this.addPurchase}}>Add Purchase</a>

<h3 class="m-ps">My Purchases</h3>

{{#each purchases as |purchase|}}
    <strong>{{purchase.id}}</strong>
    <div>{{purchase.name}}</div>
    <div>{{purchase.price}}</div>
{{/each}}

</div>

purchases.js:

import Controller from '@ember/controller';
import { action } from '@ember/object';

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

export default class PurchasesController extends Controller {
    purchases = [];

    @action
    addPurchase() {
        var pname = this.pname;
        var pprice = this.pprice;
        this.purchases.push({ id: getRandomInt(0, 999), name: pname, price: pprice });
    }
}

The rule of thumb for autotracking is that you have to explicitly track any state you want to be reactive—that is, any state you want to reflect changes to in the template.

In your specific scenario, when you just use a native array and its normal methods, that’s invisible to the reactivity system and template layer. It is, after all, just a normal array—there’s no way for Ember to know anything about it!

When you mark a property as @tracked, you’re marking the property itself as reactive, so updates to the property will notify the reactivity system and template layer—but you’re not doing anything to the contents of the property if it’s an object, array, etc.

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

class Example extends Component {
  @tracked valueType = 'some string';
  @tracked referenceType = [];

  // THIS WORKS FINE
  @action updateValueType(newValue) {
    // it sets the property marked as `@tracked` directly!
    this.valueType = newValue;
  }

  // WILL NOT WORK
  @action
  incorrectlyUpdateReferenceTypeContents(value) {
    // doesn't do anything with `referenceType` *itself*, only
    // with the things *inside* it (via the method).
    this.referenceType.push(value);
  }

  // WILL WORK
  @action
  correctlyUpdateReferenceTypeContents(key, value) {
    // sets `referenceType` *itself*, with an updated version of
    // the array
    this.referenceType = this.referenceType.concat(value);
  }
}

If you want individual items within some reference type (like an array or object) to be tracked, you need to use something which can notify the system. You can either use the “functional update” style shown in the // WILL WORK section of code there in (2), or you can use the tracked-built-ins library to get an array type which supports tracking on all the native array methods:

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { TrackedArray } from 'tracked-built-ins';
import { action } from '@ember/object';

class Example extends Component {
  @tracked referenceType = new TrackedArray();

  // WORKS NOW!
  @action
  addToArray(value) {
    // all the methods in `TrackedArray` notify the reactivity system
    // correctly, so this works exactly as you would expect!
    this.referenceType.push(value);
  }

  // WILL ALSO STILL WORK
  @action
  functionalUpdate(key, value) {
    // sets `referenceType` *itself*, with an updated version of
    // the array -- works because `referenceType` is still marked
    // as `@tracked`
    this.referenceType = this.referenceType.concat(value);
  }
}

Again, the rule of thumb is: if you want a given update to be reactive, you need to explicitly make it reactive. For an individual property, you can do that by marking it as @tracked. For something like an Array, you need an implementation of Array which knows how to tell the reactivity system that there has been an update when you call push, and then forward it to the regular Array implementation—which is exactly what TrackedArray does.

For a deep dive into how this works “under the hood”, see my blog post on Autotracking. For examples of how to implement data structures which use autotracking correctly like TrackedArray, see @pzuraq’s post Autotracking Case Study - TrackedMap and my post Async Data and Autotracking in Ember Octane.

1 Like