Using Ember's @tracked Decorator for Fine‑Grained Reactive State
Learn how to add @tracked to a component property so the template updates instantly when the value changes, with setup steps, verification tips, and recovery options.
22 May 2026, 18:10 UTC

Desired outcome
Create an Ember component whose template re‑renders automatically when a tracked property changes, without writing observers or manual property notifications.
Prerequisites
- Ember CLI project version 3.28 or later (Octane edition).
- Node.js >=14 and npm/yarn installed.
- Basic knowledge of Ember components, actions, and template syntax.
- Access to run
ember serve(development server) and optionallyember fastbootfor server‑side rendering checks.
Procedure
Ensure the project uses the Octane preset. If you created the app with Ember CLI
--preset=octaneyou can skip this step. Otherwise run:# Run in the project root npm install --save-dev @ember/octane-app-preset # Then adjust config/environment.js to enable the preset if neededGenerate a new component (you can place it anywhere; here we use
components/counter.js):# Run in the project root ember generate component counterOpen the generated component class
app/components/counter.jsand add the@trackeddecorator to a property. Import it from@glimmer/tracking:import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; export default class Counter extends Component { @tracked count = 0; increment() { this.count += 1; } }Update the component template
app/components/counter.hbsto display the tracked value and call the action:<div>Count: {{this.count}}
Increment </div>If you want to test the component in a route, add it to a template, for example
app/templates/application.hbs:<h1>Ember @tracked Demo</h1> <Counter />Start the development server:
# Run in the project root ember serveOpen
http://localhost:4200in a browser, click the Increment button, and observe the count update instantly.
Expected checks
UI updates: After each click, the displayed number should change without a full page reload.
Inspector verification: Open Ember Inspector (browser extension), select the
<Counter>component, and verify that only thecountproperty appears in the Tracked Properties list. No extra property churn should be shown in the render tree.FastBoot test (optional): In a separate terminal run
ember fastboot, then curl the route:curl http://localhost:4999. The initial HTML should contain the rendered count (starting at 0). After simulating an increment via a client‑side action, the hydrated UI should reflect the new value.
Limitations and practical verification
Mutable objects: If you track an object or array, changing its internal properties (
this.items.push(...)) will not trigger an update because the reference stays the same. To cause a update, replace the reference:this.items = [...this.items, newItem].Octane requirement: The
@trackeddecorator is only available in Ember Octane edition. Using it in a classic Ember app without the appropriate polyfills results in a runtime error like "@tracked is not a function". Ensure yourpackage.jsonincludes"@ember/octane-app-preset"or equivalent.Checking the result: Besides visual confirmation, you can add a temporary
console.loginside the component’s getter or setter to confirm that the tracked property is being read on each render. Remove the log after verification.
Recovery options
If the template does not update:
- Confirm that
@trackedis imported from@glimmer/trackingand not from a deprecated package. - Check that the component class extends
@glimmer/component(orComponentfrom@ember/componentin Octane). - Run
npm run buildorember buildto clear any stale broker caches, then restartember serve.
- Confirm that
If you need to revert to classic observers while debugging:
import Component from '@glimmer/component'; import { action } from '@ember/object'; export default class Counter extends Component { count = 0; @action increment() { this.set('count', this.count + 1); } }Replace the template usage accordingly (
{{this.count}}remains valid).If FastBoot hydration fails, ensure that any code that accesses browser‑only APIs (like
window) is guarded withif (typeof window !== 'undefined')or placed indidInsertElement.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.