Cannot read property 'get' of undefined

Hello everyone! I created a purchases controller to take data from user input, but I have an error;

routes/purchases.js:

import Route from '@ember/routing/route';

export default class PurchasesRoute extends Route {
}

controllers/purchases.js:

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

export default class PurchasesController extends Controller {
    @action
    addPurchase() {
        var pname = this.get('PurchasesController').get('pname');
        var pprice = this.get('PurchasesController').get('pprice');
        console.log(pname, pprice);
    }
}

purchases.hbs:

{{page-title "Purchases"}}

<div class="purchases">

<h3>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>

</div>

{{outlet}}

error:

Uncaught TypeError: Cannot read property 'get' of undefined
    at PurchasesController.addPurchase (purchases.js:15)

Thanks in advance for your help!

Let’s break down what this line of code is doing and I think it will help. Obviously you’re creating a local variable pname, and assigning it a value. The value you’re trying to get is a chain, which is often where you’ll see this error crop up, because one of the “links” in the chain doesn’t exist. Sometimes putting a breakpoint in can help as you can see what vars have what values.

Anyway, first you have this. In the context of your controller this is being referenced in an action (you’re using the action decorator), so it is binding the method this to the controller. So this is referring to the controller itself. Next in the chain you’re getting PurchasesController. This is the source of your error. Since this is already the controller you don’t need this “link” in the chain at all. To say that differently, since this the controller you’re trying to get PurchasesController on an instance of PurchaesController but that property doesn’t exist on the controller so it is undefined. Lastly you’re trying to get another property “pname” from something that doesn’t exist (undefined) which is why the error is thrown. So in summary you can remove the middle part of the “chain” and just make it this.get('pname').

While we’re here I’ll give you a few other recommendations which aren’t strictly necessary but may help a bit. First of all, with Ember Octane (which I’m assuming you are using since you’re using native classes) you don’t usually need to use the get() method, so you could shorten your action to:

    @action
    addPurchase() {
        var pname = this.pname;
        var pprice = this.pprice;
        console.log(pname, pprice);
    }

and it would work the same way. It might also be worth declaring pname and ppriceas tracked properties. e.g.

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

export default class PurchasesController extends Controller {
  @tracked pname;
  @tracked pprice;

This tells ember to auto-track the values. For more information on autotracking see this page and this page

Lastly, it’s good practice in modern code to make sure template references to things on the backing class (the controller in this case) are prefixed with this, so your inputs should look like this:

    {{input placeholder="Purchase Name" type="text" class="validate" value=this.pname}}

Anyway hope all that helps!

1 Like

THANK you so much! It helps!