'app' is 'undefined' when using `$set` on a button

just want to set a value when the button is clicked and have it updated in the UI. what am i missing?

here’s the entire code:

<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
  <div id="app">
    <button @click="app.$set(app.item, 'uuid', getNewUUID())">
      click me
    </button>
    <div>UUID: {{ item.uuid }}</div>
  </div>  <!-- end of div#app -->
<!-- JAVASCRIPT ONLY BELOW THIS POINT  -->
<script type="text/javascript" src="../_js/axios.0.19.2.min.js"></script>
<script type="text/javascript" src="../_js/vue.2.6.11.js"></script>
<script>
 var app = new Vue({
  el: '#app',
  data: {
    item: {},
  },
  methods: {
    async getNewUUID() {  //<----  works flawlessly
      let uuid = await axios.get('getNewUUID.php')
      console.log({uuid}, uuid.data)
      return uuid.data
    },  //  getNewUUID
  },
})
</script>
</body>
</html>

as soon as i click the button i get this error:

TypeError: can't access property "$set", app is undefined

and these warnings:

[Vue warn]: Property or method "app" is not defined on the instance but referenced
 during render
[Vue warn]: Error in v-on handler: "TypeError: can't access property "$set",
 app is undefined"

sigh. how is app not defined? i tried simply doing an assignment: itemuuid = getNewUUID(), which generated an id and logged it to console, but didn’t update the UI. while i don’t know why, i DO know that in such cases, a $set is used to trigger the reactivity. and it works when in a method, it just doesn’t work when placed on a UI element. i don’t want to have to create three methods to do the same thing for various implementations, i just want one function. i know. crazy, right? :stuck_out_tongue:

EDIT: i know i flagged this, and that’s fine, but i would like to know why this is doing what it’s doing. i just don’t want to be a PITA…

This is a little bit of a tricky one if you’re not used to how Vue handles the template within its mounted state.

Basically, everything within <div id="app"> gets converted by Vue’s rendering function so that it can do its magic. What this also means is that anything within the template must be accessible through the Vue instance. So in order to access anything in the template, it must be part of one of Vue’s options properties (e.g. data, computed, methods, props, etc.).

The reason referencing app as you have done throws an error is because it sits outside of Vue’s instance. To resolve your issue, you just need to remove app because $set is already part of Vue’s instance by default and item is part of your data.

<button @click="$set(item, 'uuid', getNewUUID())">

you know, i was going to try this, but then stopped myself with the thought that there was no object to connect the method to… not because i was being clever, it just seemed like the last thing i hadn’t thrown at the wall…

thank you for explaining this clearly. i DO seem to always be finding the weird corner-cases, don’t i… :stuck_out_tongue_closed_eyes:

Where did you try doing this? If you tried to do it inline within the template it didn’t update the value because of the async nature of the function. Basically, don’t assign values from async methods within templates (Vue cannot handle async assignments within the template). Instead, use a method to assign the value.

okay. i replaced the button with your code mentioned above, and the {{ item.uuid }} now simply says [object Promise]… which seems like it’s on the right path… but isn’t resolving…?? which is odd, because the console logs an ID immediately.

Ah, it’s the same thing I was just talking about in my 2nd comment. You can’t use async methods within the template.

If you move your $set(item, 'uuid', getNewUUID()) to a method and call that method in the template then everything should work.

<button @click="myMethod">

methods: {
  myMethod() {
    this.$set(this.item, 'uuid', this.getNewUUID())
  }
}

see, i don’t want to do that. it just takes me back to the ‘three similar methods’ thing. i want to have the getNewUUID called and set on different items. to whit:

<div>
  <button @click="$set(job, 'uuid', getNewUUID())">New Job UUID</button>
  <h2>{{ job.uuid }}</h2>
</div>
<div>
  <button @click="$set(invoice, 'id', getNewUUID())">New Invoice UUID</button>
  <h2>{{ invoice.id }}</h2>
</div>

I hear you, but this is the way it is with Vue 2. Here’s how I’d go about it

<div>
  <button @click="setId(job, 'uuid')">New Job UUID</button>
  <h2>{{ job.uuid }}</h2>
</div>
<div>
  <button @click="setId(invoice, 'id')">New Invoice UUID</button>
  <h2>{{ invoice.id }}</h2>
</div>

This I would abstract to its own module

const getNewUUID = async () => {
  try {
    const { data } = await axios.get('getNewUUID.php')
    return data
  } catch (error) {
    return null
  }
}

export default getNewUUID
methods: {
  async setId (obj, key) {
    const uuid = await getNewUUID();
    this.$set(obj, key, uuid);
  }
}

yeah. this is how i wound up breaking out myself in the interim, although not as elegantly… and i didn’t use a module, because i’m still SPAing. :slight_smile: and i don’t mind that that’s the limitation of the framework. i just struggle with knowing that it is, if you get my drift. thank you so much for patiently explaining.

and i am inferring that things are somewhat different in Vue3? :smiley: i’ll get there soon… only to start bashing into all it’s corner-cases… :stuck_out_tongue:

EDIT: occurred to me you might want to see what i did:

<button @click="getNewUUID(item, 'uuid')">click me</button>
<div><h1 :key="item.uuid">UUID: {{ item.uuid }}</h1></div>
  methods: {
    async getNewUUID(obj, key) {
      let uuid = await axios.get('getNewUUID.php')
      app.$set(obj, key, uuid.data)
    },  //  getNewUUID
  }