1

I'm trying to call an API and once I have the response I want to target the image_url property but I'm getting this error Error in render: "TypeError: Cannot read property 'image_url' of undefined" and Cannot read property 'image_url' of undefined at VueComponent.getImages1 (Info.vue?42ed:39)

I've tested my code in another Javascript file and over there it works but for some reason not here. I've also checked for if the parent state is sending data correctly to child state through Vue console and it has worked.

Info.vue

<template>
  <section>
    <img :src="getImages1()" alt="./assets/notFound.png" />
    <img :src="getImages2()" alt="./assets/notFound.png" />
  </section>
</template>

<script>
import axios from "axios";

export default {
  props: {
    anime1: String,
    anime2: String,
  },

  methods: {
    animeFind(anime) {
      axios
        .get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
        .then(async function(response) {
          const id = await response.data["results"][0]["mal_id"];
          await axios
            .get(`https://api.jikan.moe/v3/anime/${id}`)
            .then(function(response) {
              return response.data;
            })
            .catch(function(error) {
              return error; // take care of this later
            });
        })
        .catch(function(error) {
          return error; // take care of this later
        });
    },
    // eslint-disable-next-line no-unused-vars
    getImages1() {
      let response = this.animeFind(this.anime1);
      return response["image_url"];
    },
    getImages2() {
      let response = this.animeFind(this.anime2);
      return response["image_url"];
    },
  },
};
</script>
<style></style>

I tried doing this and it worked

main.js

const axios = require("axios");

const animeFind = (anime) =>
    axios
        .get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
        .then(async function (response) {
            const id = await response.data["results"][0]["mal_id"];
            await axios
                .get(`https://api.jikan.moe/v3/anime/${id}`)
                .then(function (response) {
                    console.log(response.data["image_url"]);
                })
                .catch(function (error) {
                    console.log(error);
                });
        })
        .catch(function (error) {
            console.log(error);
        });

animeFind("Naruto");
animeFind("Cowboy Bebop");

This is the parent component, when the button is clicked only then should the image change

<template>
  <section class="hero">
    <div class="parent-1">
      <h1 class="title is-1">Compare two animes! :)</h1>
    </div>

    <div class="columns">
      <div class="column">
        <b-field class="label" label="Anime 1">
          <b-input value="Enter the first anime!" v-model="anime1"></b-input>
        </b-field>
      </div>
      <div class="column">
        <b-field class="label" label="Anime 2">
          <b-input value="Enter the second anime!" v-model="anime2"></b-input>
        </b-field>
      </div>
    </div>
    <div class="button-spacing">
      <b-button class="button" type="is-primary" @click="checkComplete"
        >Compare!</b-button
      >
    </div>

    <Info :anime1="anime1" :anime2="anime2" v-if="success">Wow</Info>
  </section>
</template>

<script>
import Vue from "vue";
import Buefy from "buefy";
import "buefy/dist/buefy.css";
import Info from "./Info.vue";
Vue.use(Buefy);

export default {
  components: {
    Info,
  },
  data() {
    return {
      anime1: "",
      anime2: "",
      success: false,
    };
  },

  methods: {
    // log() {
    //   console.log(this.anime1);
    //   console.log(this.anime2);
    // },
    checkComplete() {
      if (this.anime1.length > 0 && this.anime2.length > 0) {
        // let animeData1 = this.animeFind(this.anime1);
        // let animeData2 = this.animeFind(this.anime2);
        this.success = true;
        return this.$buefy.toast.open({
          message: "Yay, just a moment now!",
          type: "is-success",
          position: "is-bottom",
          duration: 3000,
        });
      }
      this.success = false;
      return this.$buefy.toast.open({
        duration: 3000,
        message: `Please fill out both fields`,
        position: "is-bottom",
        type: "is-danger",
      });
    },
  },
};
</script>

2 Answers 2

2

I think you're still a little confused with promises. your animFind function is not returning anything.

Instead try

<template>
  <section>
    <img :src="url1" alt="./assets/notFound.png" />
    <img :src="url2" alt="./assets/notFound.png" />
  </section>
</template>

<script>
import axios from "axios";

export default {
  props: {
    anime1: String,
    anime2: String,
  },
  data() {
    return {
      url1: '',
      url2: '',
      error: ''
    }
  },
  methods: {
    animeFind(anime, data) {
      axios
        .get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
        .then(response => {
          const id = response.data["results"][0]["mal_id"];
          axios
            .get(`https://api.jikan.moe/v3/anime/${id}`)
            .then(response => this[data] = response.data["image_url"]);
        })
        .catch(error => {
          this.error = error; // take care of this later
        });
    }
  },
  
  watch: {
    anime1: {
      immediate: true,
      handler(newVal, oldVal) {
        this.animeFind(newVal, 'url1');
      },
    },
    anime2: {
      immediate: true,
      handler(newVal, oldVal) {
        this.animeFind(newVal, 'url2');
      },
    },
  },
};
</script>

Notice the use if arrow functions to stay in the vue scope

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you, I'm just wondering why you used this[data] and not this.data. Also, if I change one of the inputs it doesn't do the change in image. I have to refresh again and type it and then I can see a new image
be cos this.data means the "data" parameter where as this[data] means this."the value of data"
OH, for the not updating issue, move the code inside mounted() into watchers for anime1 and anime2 ... i will edit the response
Is there a way to do this only after I have pressed a button, I will post the code that has the is the parent component
You could put the code in a method of the child component and call it from the parent using refs or you could do the ajax stuff in the parent and pass the urls as props
1

The getImages() function return before the animeFind() would return. So the getImages() will return undefined.

You can put the axios call into hooks and when you return the response.data object, you can assign it to a property in the data object. You use this property instead the function in the template, so the component will be reactive. Notice that you should use regular function on the outer function in the axios call and arrow functions on the then() responses for getting a proper this.

I am taking care of only one image example for simplicity, but editing this is not so complicated.

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
  <section>
    <p>Show image here</p>
    <img :src="urlResponse['image_url']" alt="./assets/notFound.png">
  </section>
</template>

<script>
import axios from "axios";

export default {
  data() {
    return {
      urlResponse: {}
    };
  },

  props: {
    anime1: String,
    anime2: String
  },

  created() {
    this.animeFind(this.anime1);
  },

  updated() {
    this.animeFind(this.anime1);
  },

  methods: {
    animeFind: function(anime) {
      axios
        .get(`https://api.jikan.moe/v3/search/anime?q=${anime}`)
        .then(async response => {
          const id = await response.data["results"][0]["mal_id"];
          await axios
            .get(`https://api.jikan.moe/v3/anime/${id}`)
            .then(response => {
              this.urlResponse = Object.assign(
                {},
                this.urlResponse,
                response.data
              );
              return response.data;
            })
            .catch(function(error) {
              return error; // take care of this later
            });
        })
        .catch(function(error) {
          return error; // take care of this later
        });
    }
  }
};
</script>
<style></style>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.