Cache size: {{this.userCache.size}}
} ``` ### Reactive Sets Sets are useful for unique collections: ```glimmer-js // app/components/tag-selector.gjs import Component from '@glimmer/component'; import { action } from '@ember/object'; import { trackedSet } from '@ember/reactive/collections'; export default class TagSelector extends Component { selectedTags = trackedSet(); @action toggleTag(tag) { if (this.selectedTags.has(tag)) { this.selectedTags.delete(tag); } else { this.selectedTags.add(tag); } } get selectedCount() { return this.selectedTags.size; }Selected: {{this.selectedCount}} tags
} ``` ### When to Use Each Type | Type | Use Case | | -------------- | ------------------------------------------------------------------ | | `trackedArray` | Ordered lists that need mutation methods (push, pop, splice, etc.) | | `trackedMap` | Key-value pairs with non-string keys or when you need `size` | | `trackedSet` | Unique values, membership testing | ### Common Patterns **Initialize with data:** ```javascript import { trackedArray, trackedMap, trackedSet } from '@ember/reactive/collections'; // Array const todos = trackedArray([ { id: 1, text: 'First' }, { id: 2, text: 'Second' }, ]); // Map const userMap = trackedMap([ [1, { name: 'Alice' }], [2, { name: 'Bob' }], ]); // Set const tags = trackedSet(['javascript', 'ember', 'web']); ``` **Convert to plain JavaScript:** ```javascript // Array const plainArray = [...trackedArray]; const plainArray2 = Array.from(trackedArray); // Map const plainObject = Object.fromEntries(trackedMap); // Set const plainArray3 = [...trackedSet]; ``` **Functional array methods still work:** ```javascript const todos = trackedArray([...]); // All of these work and are reactive const completed = todos.filter(t => t.done); const titles = todos.map(t => t.title); const allDone = todos.every(t => t.done); const firstIncomplete = todos.find(t => !t.done); ``` ### Alternative: Immutable Updates If you prefer immutability, you can use regular `@tracked` with reassignment: ```javascript import { tracked } from '@glimmer/tracking'; export default class TodoList extends Component { @tracked todos = []; @action addTodo(text) { // Reassignment is reactive this.todos = [...this.todos, { id: Date.now(), text }]; } @action removeTodo(id) { // Reassignment is reactive this.todos = this.todos.filter((t) => t.id !== id); } } ``` **When to use each approach:** - Use reactive collections when you need mutable operations (better performance for large lists) - Use immutable updates when you want simpler mental model or need history/undo ### Best Practices 1. **Don't mix approaches** - choose either reactive collections or immutable updates 2. **Initialize in class field** - no need for constructor 3. **Use appropriate type** - Map for key-value, Set for unique values, Array for ordered lists 4. **Export from modules** if shared across components Reactive collections from `@ember/reactive/collections` provide the best of both worlds: mutable operations with full reactivity. They're especially valuable for large lists or frequent updates where immutable updates would be expensive. **References:** - [Ember Reactivity System](https://guides.emberjs.com/release/in-depth-topics/autotracking-in-depth/) - [JavaScript Built-in Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) - [Reactive Collections RFC](https://github.com/emberjs/rfcs/blob/master/text/0869-reactive-collections.md)