13

I'm using Vue Resource to retrieve an images collection from a REST API. The request is sent in the created hook of my Vue component.

The problem is, I'm trying to access the retrieved data in the mounted hook, but the data isn't loaded.

I get this error in the console:

[Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'forEach' of undefined"

Here is my component:

<script>
export default {
  data() {
    return { imgs : '' };
  },
  created() {
    // the full url is declare in my main.js
    this.imgs = this.$resource('acf/v3/pages/4');

    this.imgs.query().then((response) => {
      console.log('success', response);
      this.imgs = response.data.acf.gallery;
    }, (response) => {
      console.log('erreur', response);
    });
  },
  mounted() {
    // get the ref="image" in my dom template
    let imgs = this.$refs.image;

    imgs.forEach((img) => {
      // I do some stuff with imgs
    });
  }
}
</script>

If I wrap a setTimeout around the content of mounted, everything works fine.

So, I don't understand how I can wait for my data to load before the mounted hook is executed. Isn't this the role of the Vue lifecycle hooks?

thanksd
  • 54,176
  • 22
  • 157
  • 150
BrownBe
  • 977
  • 3
  • 11
  • 23
  • why don't you just use created()? – samayo Dec 08 '17 at 15:37
  • 1
    Because nothing is reactive in `created`. You can't manipulate the DOM before `mounted` hook. [check this documentation](https://alligator.io/vuejs/component-lifecycle/). And this is exactly the same issue if I use only one lifeCycle hook. My content isn't loaded when I would like to use it. – BrownBe Dec 08 '17 at 15:47

2 Answers2

12

Since the this.imgs.query() call is async, your mounted hook is being called before the then handler is setting this.imgs (which I'm assuming is being bound with v-for to an element in your template with an attribute ref="image"). So, even though the component has been mounted to the DOM, the $refs have not been set up yet.

I would make a method to "do some stuff with imgs" and then call that method in a $nextTick callback in the then handler of the async call. The callback passed to $nextTick will be "executed after the next DOM update cycle", meaning the $refs will be set up at that point.

<script>
export default {
  data() {
    return { imgs: '' };
  },
  created() {
    // the full url is declare in my main.js
    this.imgs = this.$resource('acf/v3/pages/4');

    this.imgs.query().then((response) => {
      console.log('success', response);
      this.imgs = response.data.acf.gallery;
      this.$nextTick(() => this.doStuffWithImgs());
    }, (response) => {
      console.log('erreur', response);
    });
  },
  methods: {
    doStuffWithImgs() {
      // get the ref="image" in my dom template
      let imgs = this.$refs.image;

      imgs.forEach((img) => {
        // I do some stuff with imgs
      });
    }
  }
}
</script>
tony19
  • 125,647
  • 18
  • 229
  • 307
thanksd
  • 54,176
  • 22
  • 157
  • 150
  • I didn't know [$nextTick](https://fr.vuejs.org/v2/api/index.html#Vue-nextTick). Thanks, it's working as expected right know. – BrownBe Dec 08 '17 at 16:19
  • @thanksd is this how its usually done because i guess all vuejs app some way or other use data from some api and then create dom based on it.i want to know is this how its done usually – anekix Mar 19 '18 at 09:19
  • @anekix There are many ways to handle general async behavior in a Vue app. If you need to affect data in the `this.$refs` array before the component has been mounted, then yes, using `$nextTick` is the best and most common way. – thanksd Mar 19 '18 at 13:15
3

As shown in the Lifecycle Diagram of Vue instance. After Mounted Hook (which means we can access DOM), there is also beforeUpdate and updated hooks. These hooks can be used when data is changed. I think beforeUpdate or update hook can be used after getting data in created hook.

<script>
   export default {
      data() {
         return { imgs : '' };
       },
   created() {
    // the full url is declare in my main.js
    this.imgs = this.$resource('acf/v3/pages/4');

    this.imgs.query().then((response) => {
        console.log('success', response);
        this.imgs = response.data.acf.gallery;
       }, (response) => {
         console.log('erreur', response);
     });
   },
  // here we can use beforeUpdate or updated hook instead of mounted
  beforeUpdate() {
    // get the ref="image" in my dom template
    let imgs = this.$refs.image;

    imgs.forEach((img) => {
    // I do some stuff with imgs
   });
 }
}
I hope this helps.
tony19
  • 125,647
  • 18
  • 229
  • 307