Skip to content

Latest commit

 

History

History
154 lines (127 loc) · 5.46 KB

no-assignment-of-untracked-properties-used-in-tracking-contexts.md

File metadata and controls

154 lines (127 loc) · 5.46 KB

ember/no-assignment-of-untracked-properties-used-in-tracking-contexts

💼 This rule is enabled in the ✅ recommended config.

🔧 This rule is automatically fixable by the --fix CLI option.

Ember 3.13 added an assertion that fires when using assignment this.x = 123 on an untracked property that is used in a tracking context such as a computed property.

You attempted to update "propertyX" to "valueY", but it is being tracked by a tracking context, such as a template, computed property, or observer.

In order to make sure the context updates properly, you must invalidate the property when updating it.

You can mark the property as @tracked, or use @ember/object#set to do this.

Rule Details

This rule catches assignments of untracked properties that are used as computed property dependency keys.

Examples

Examples of incorrect code for this rule:

import { computed } from '@ember/object';
import Component from '@ember/component';

class MyComponent extends Component {
  @computed('x') get myProp() {
    return this.x;
  }
  myFunction() {
    this.x = 123; // Not okay to use assignment here.
  }
}

Examples of correct code for this rule:

import { computed, set } from '@ember/object';
import Component from '@ember/component';

class MyComponent extends Component {
  @computed('x') get myProp() {
    return this.x;
  }
  myFunction() {
    set(this, 'x', 123); // Okay because it uses set.
  }
}
import { computed, set } from '@ember/object';
import Component from '@ember/component';
import { tracked } from '@glimmer/tracking';

class MyComponent extends Component {
  @tracked x;
  @computed('x') get myProp() {
    return this.x;
  }
  myFunction() {
    this.x = 123; // Okay because `x` is a tracked property.
  }
}

Migration

The autofixer for this rule will update assignments to use set. Alternatively, you can begin using tracked properties.

Configuration

  • object -- containing the following properties:
    • array -- extraMacros -- Array of configurations for custom computed property macros which have dependent keys as arguments, each with hte following properties:
      • string -- name -- The name the macro is exported with
      • string -- path -- The file path used for importing the macro
      • string -- indexName -- If this macro can also be imported through an index (like computed for computed.and), include it here
      • string -- indexPath -- The path for importing the index. For example, with import { computed } from '@ember/object' and computed.and(...), @ember/object is the indexPath and computed is the indexName.
      • array -- argumentFormat -- array of configurations for how to parse the arguments of the macro to extract the computed dependencies, with at least one of the following properties:
        • object -- strings -- Configuration for extracting raw strings from the argument list, with the following options:
          • number -- count -- How many arguments to consider as dependencies. Use Number.MAX_VALUE for all of them.
          • number -- startIndex -- Defaults to zero. If it's something else, that many arguments will be skipped before checking for count dependencies.
        • object -- objects -- Configuration for extracting the values of an object as dependency keys, with the following properties:
          • number -- index -- The index of the argument to be checked.
          • array -- keys -- Array of strings for which keys values should be checked for. If not provided, all values will be checked.

Example configuration:

module.exports = {
  rules: {
    'ember/no-assignment-of-untracked-properties-used-in-tracking-contexts': {
      extraMacros: [
        {
          name: 'rejectBy',
          path: 'custom-macros/macros',
          indexName: 'customComputed',
          indexPath: 'custom-macros',
          argumentFormat: [
            {
              strings: {
                count: 1
              }
            }
          ]
        },
        {
          name: 't',
          path: 'ember-intl',
          argumentFormat: [
            {
              objects: {
                index: 1
              }
            }
          ]
        }
      ]
    }
  }
};

This configuration works for the t macro from ember-intl, and a custom rejectBy macro that behaves similarly to filterBy (with the second string argument not being a dependency):

import { A, isArray } from '@ember/array';
import { get } from '@ember/object';

export default function rejectBy(dependentKey, propertyKey, value) {
  return computed(`${dependentKey}.@each.${propertyKey}`, function () {
    const parent = get(this, dependentKey);
    if (!isArray(parent)) {
      return A();
    }
    const callback =
      arguments.length === 2
        ? (item) => !get(item, propertyKey)
        : (item) => get(item, propertyKey) !== value;
    return A(parent.filter(callback));
  });
}

References