5
votes

I have a Vue component where I am trying to fetch some data from an API using axios.

<template>
    <div>
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
      fetchData() {
        const customJs = new CustomJS();
        return customJs.getTools();
      }
  },
  created() {
    this.tools = this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>

The getTools() function is in a different JS file outside the Vue component file which makes the API call using axios.get.

getTools(id = 0){
    this.apiTool += (id > 0) ? id : '';
    axios.get(this.apiTool, {
    })
    .then(function (response) {
        console.log(response.data);
        return response.data;
    })
    .catch(function (error) {
        console.log(error);
    });
}

The problem is, {{tools}} is undefined since the getTools() takes some time to return the response data. How can wait for the response data and then return?

7
you can use synchronous http request in the getTools(), if you want to wait for the request to complete - Sagar Bahadur Tamang
@SagarTamang No, not a good idea. sync is deprecated, please don't advice people to use a feature that could disappear at any moment. - Keith
What is is you need to wait for,.. can you not just do -> this.fetchData().then(......? - Keith
@Keith Ok, I will keep in my mind. Thanks a lot. - Sagar Bahadur Tamang
I got a partial solution here- stackoverflow.com/questions/43613115/… - Souvik Ghosh

7 Answers

8
votes

Try the code below: so the code will render only when its actually loaded

<div v-if="tools">
    This is Default child component
    {{tools[0].name}}
</div>
3
votes

Usually for that, I am using a loader to show the user that a request is in progress

<div v-if="loading">
  <loader /> //a loader component
</div>

<div v-else>
  // show the template since request finished
</div>

and the script

export default {
  data: () => ({
    loading: false
  }),

  created() {
   this.loading = true
   axios.get('api') //your request
   .then(response => console.log(response))
   .finally(() => (this.loading = false)) //when the requests finish
  }

}

If you don't like the way above, you can use beforeEnter so the route will load only when the request finishes:

{
  path: '/my-route',
  component: YourComponent,
  props: true,
  beforeEnter (to, from, next) {
    axios.get('api-request')
     .then(response => {
      to.params.data = response //we pass data through props to the component
      next()
     })
  }
}
2
votes
<template>
    <div v-if="isGetTools">
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  data: function () {
    return {
      isGetTools: false
    }
  },
  methods: {
      fetchData() {
        const customJs = new CustomJS();
        this.tools = customJs.getTools();
        this.isGetTools = true;
      }
  },
  created() {
    this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>

Try to add v-if in your div. And update the isGetTools to true after getting the result from AXIOS

0
votes

What you want to do is to define your getTools function as a promise like this:

getTools (id = 0) {
  return new Promise((resolve, reject) => {
    this.apiTool += (id > 0) ? id : '';
    axios.get(this.apiTool, {
    })
    .then(function (response) {
        console.log(response.data);
        resolve(response.data);
    })
    .catch(function (error) {
        console.log(error);
        reject(error)
    });
  })
}

Then you can use it in your component code as:

<template>
    <div>
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
      fetchData() {
        const customJs = new CustomJS();
          customJs.getTools().then((result) => {
            return result;
          }
        )

      }
  },
  created() {
    this.tools = this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>

Or using async/await:

<template>
    <div>
        This is Default child component
        {{tools[0].name}}
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
      async fetchData() {
      const customJs = new CustomJS();
      return await customJs.getTools()  
      }
  },
  created() {
    this.tools = this.fetchData(); //preferably need to wait here wait for response
  }
}
</script>
0
votes

You need to return a promise from your request

<template>
<div>
    This is Default child component
    {{tools[0].name}}
</div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
      tools: []
  },
  methods: {
      fetchData() {
         const customJs = new CustomJS();
         return new Promise((resolve, reject) => {
             customJs.getTools()
                 .then(res => resolve(res))
                 .catch(err => reject(err))
        })
     }
  },
  created() {
      this.fetchData().then(res => {
         this.tools = res);
      } //preferably need to wait here wait for response
    }
   }
 </script>
0
votes

Give a try to retrieve data on mounted

    <template>
    // try adding this condition on div element.
                <div v-if="tools.length">
                    This is Default child component
                    {{tools[0].name}}
            </div>
        </template>

        <script>
        import { CustomJS } from '../js/custom.js';

        export default {
          name: 'HomeContent',
          props: {
            tools: []
          },
          methods: {
              fetchData() {
                const customJs = new CustomJS();
                return customJs.getTools();
              }
          },
          mounted: function () {
            this.tools = this.fetchData();    
            // or    
            // const customJs = new CustomJS();
            // this.tools =  customJs.getTools();
          }
        }
        </script>
0
votes

I don't know why this question pops up in my "timeline" and haven't got the accepted answer yet so I'll answer it anyway. The problem seemed to be with the understanding of the OP with asynchronous code in JS. Here's the fix (there're 3) to make it better:

<template>
    <div v-if="tools.length">
        {{tools[0].name}}
    </div> 
    <div v-else> // I. Use v-if/v-else to conditionally show data, in Vue 3 there will be another feature called "suspense" borned to do those things: https://vueschool.io/articles/vuejs-tutorials/suspense-new-feature-in-vue-3/
        This is Default child component
    </div>
</template>

<script>
import { CustomJS } from '../js/custom.js';

export default {
  name: 'HomeContent',
  props: {
    tools: []
  },
  methods: {
    async fetchData() {
      const customJs = new CustomJS();
      this.tools = await customJs.getTools(); 
      // II. The OP assigned a promise to this.tools, this.tools wasn't being assigned to any actual data at all, because: 
      // 1. getTools() didn't return any data
      // 2. even if it returned data, it wasn't being promise-resolved (by await/then) before assigned to this.tools
    }
  },
  created() {
    this.fetchData();
  }
}
</script>

async getTools(id = 0){
  this.apiTool += (id > 0) ? id : '';

  try {
    const response = await axios.get(this.apiTool, {});
    console.log(response.data);
    return response.data; 
    // III. The OP didn't return any fetched data at all, it just called API then do nothing. All the returns were returned in the arrow functions, the actual function getTools didn't get any return

  }
  catch (err) {
    console.log(err)
  }
},