Glimmer.js中filtered属性触发UntrackedPropertyError求助
filtered Property in Glimmer.js Hey there! Let's break down why you're seeing that UntrackedPropertyError and how to fix it.
The Root Cause
Glimmer.js's reactive system relies on the @tracked decorator to know when properties change so it can update the template. In your code, the filtered property is part of plain JavaScript objects in your dogHouse array—these nested properties aren't tracked by default. When your template accesses {{dog.filtered}}, Glimmer detects that this property isn't being tracked and throws the error to prevent stale UI state.
Step-by-Step Solution
1. Create a Tracked Dog Class
First, define a class for your dog objects where all properties (including filtered) are decorated with @tracked. This tells Glimmer to monitor changes to these properties:
import { tracked } from '@glimmer/tracking'; import Component from '@glimmer/component'; class Dog { @tracked breed; @tracked image; @tracked filtered; constructor(breed, imageUrl) { this.breed = breed; this.image = imageUrl; this.filtered = false; // Default to showing all dogs initially } } export default class DoggyAppGlimmer extends Component { @tracked dogHouse = []; @tracked filterValue = ''; // Rest of your component code goes here... }
2. Use the Tracked Class When Fetching Data
Update your didInsertElement hook to create Dog instances instead of plain objects. This ensures every dog's properties are tracked, plus we'll use Promise.all to handle all fetch requests more efficiently:
didInsertElement() { fetch('https://dog.ceo/api/breeds/list/all') .then(response => response.json()) .then(data => { const breedPromises = Object.keys(data.message).map(breed => { return fetch(`https://dog.ceo/api/breed/${breed}/images/random`) .then(response => response.json()) .then(image => new Dog(breed, image.message)); }); Promise.all(breedPromises).then(dogs => { this.dogHouse = dogs; }); }); }
3. Simplify the Filter Handler
Now that filtered is a tracked property, you don't need to reassign the entire dogHouse array to trigger updates. Just update each dog's filtered property directly:
filterHandler(e) { this.filterValue = e.target.value.trim().toLowerCase(); const filter = this.filterValue; this.dogHouse.forEach(dog => { // Make filtering case-insensitive for better user experience dog.filtered = dog.breed.toLowerCase().includes(filter); }); }
4. Tweak the Template for Reliability
Your existing template works, but swapping onkeyup for the input event makes it trigger for all value changes (like pasting text), which is more reliable:
<div> dog house <input value={{filterValue}} placeholder="Find a doggo" {{on "input" this.filterHandler}} /> {{#each this.dogHouse key="@index" as |dog|}} {{#if dog.filtered }} <div> <h1>{{dog.breed}}</h1> <img src={{dog.image}} alt={{dog.breed}} /> </div> {{/if}} {{/each}} </div>
Why This Works
By wrapping your dog data in a tracked class, you're telling Glimmer exactly which properties to monitor. When you update dog.filtered, Glimmer detects the change and automatically re-renders the relevant parts of the template—no need to recreate the entire array.
内容的提问来源于stack exchange,提问作者Joseph Morales

