Returning promise from api and putting into global data

Hello,
Sorry if this is obvious but new to vue and my js not so great…

I’m trying to get some data from an api and then put that data into a global object so it can be passed down to some child components.

I can’t seem to $set the data into the global projects object, it either returns undefined or returns a promise.

export default {
name: ‘app’,
components: {
projectsList
},
data: function() {
return {
users:’’,
projects:’’,

     }
},

methods: {},

created : function () {
var response = client.getEntries().then((response) => {
console.log(response)
return response
})
this.$set(this, ‘projects’, response)
console.log(this.projects)
},
}

any help much appreciated…

Your response variable will always be a promise for the result of the api call. You probably don’t actually want to capture that as your variable. In addition, if the api call hasn’t finished by the time you call this.$set, response will be undefined.

Instead, try calling $set inside the .then():

created : function () {
  client.getEntries().then((response) => {
    console.log(response)
    this.$set('projects', response)
    console.log(this.projects)
    return response
  });
}

If there are any methods that depend on this.projects, you’ll want to call them inside the .then(), or do a check for this.projects before running the method:

methods: {
  doSomethingWithProjects: function() {
    if (!this.projects) return;
    // do stuff with this.projects, if it exits
    return somethingElse
  }
}

Thanks very much!
my problem now is that even if i pass ‘projects’ down to my child component as a prop,
it still returns undefined…
i guess i need to make sure the data has loaded in the root instance first ?

Put this in the root component:

computed: {
  computedProject: function() {
    if (!this.project) return null;
    return this.project;
  }
}

And then pass computedProject to your child component as a prop. This way, it will be null until the api call completes, at which point it will be whatever data was returned from the api.

thanks again! it’s still returning undefined in my child component though.
parent excerpt -
created : function () {
client.getEntries().then((response) => {
console.log(response)
this.projects = response
console.log(this.projects)
return response
});
},
computed: {
computedProject: function() {
if (!this.projects) return null;
return this.projects;
}
}

child -

props :[‘computedProject’],
created : function () {
console.log(this.projects)
},

Can you include how you’re passing computedProject to the child component?

erm sorry confused. as a prop right there?

heres my root component data if thats any help

data: function() {
return {
users:’’,
projects:’’,

     }
},

You need to pass it explicitly, somewhere.

    <parent-component>
        <child-component :computed-project="computedProject"></child-component>
    </parent-component>

Is this what you’re doing?

1 Like

not yet ! this is the html from my child comp -

<template>
<section class="project-list" >
       <swiper :options="swiperOption">
        <swiper-slide v-for="project in projects" v-bind:ref="'project' + project.name" :key="project.name" data-swiper-parallax="-100">
            <div class="project-cover wrapper">
                {{project.name}}
                <router-link :to="{ name: 'project', params: { id: project.name }}">
                    <div class="cover-img" :style="{ 'background-image': 'url(../static/img/projects/'+project.name+'/'+'project-details-header.jpg)' }"> </div>

                    <div class="project-copy"></div>
                    <div href="#" class="tilter tilter--4">
                        <figure class="tilter__figure">
                            <img class="tilter__image" :src="'+project.fields.coverImage.fields.file.url+'" alt="" />
                            <div class="tilter__deco tilter__deco--shine"></div>
                            <div class="tilter__deco tilter__deco--overlay" v-bind:style="{ backgroundImage: project.gradient }"></div>
                            <figcaption class="tilter__caption">
                                <h1 class="projectName tilter__title title">{{project.name}}</h1>
                                <h2 class="tilter__description">{{project.tagline}}</h2>
                            </figcaption>
                            <svg class="tilter__deco tilter__deco--lines" viewBox="0 0 300 415">
                                <path d="M20.5,20.5h260v375h-260V20.5z" />
                            </svg>
                        </figure>
                    </div>
                    </router-link>

            </div>

            </swiper-slide>
				 <div class="swiper-pagination" slot="pagination"></div>
     </swiper>
 </section>
</template>

Can you show me what the parent template looks like? This is where you need to pass the property to the child component:

Parent.vue

<parent>
    <child :computed-project="computedProject"></child>
</parent>

Then, you register the property in the script of the child:

Child.vue:

<template></template>
<script>
    ...
    props: ['computedProject'],
    ...

Then, the methods & template of Child will have access to computedProject:

<template>
    <div>{{computedProject}}</div>
</template>
<script>
    methods: {
        coolMethod: function() {
            // do stuff with 
            // this.computedProject
        }
    },
    events: {}
    ...
</script>
1 Like

parent template looks like this…

 <template>
  <div id="app">
     <section class="intro">
         <h1>WELCOME</h1>
     </section>
      <transition name="fade"> <router-view class="view"></router-view></transition>
  </div>
</template>
<script>

thanks!

i guess this might be slightly more complicated because i’m using vue-router?

Is your child component the view rendered by <router-view>? If that is the case, you should be able to do:

Template of parent component:

<template> 
    <router-view :computed-project="computedProject"></router-view>
</template>

EDIT - I made a mistake a couple replies above - when you use data-binding syntax like this, you shouldn’t need curly braces when you pass data to a child.

1 Like

mega thank you ! all working now …

ok so i’m still struggling… any help appreciated!

no matter what i try, i still can’t seem to be able to access the computedProject data in my child component.
my template is rendering fine , and i can see the prop has loaded in the vue inspector, but if i console log it in my child component, it still reads undefined…

Can you share the code you’re using to try and access the property in the child component? Based on an earlier comment of yours, you might be accessing it using the wrong name. Any property passed in from a parent component is available as this.<propertyName> in the child component.

In the parent template, pass the property using kebab-case: <child-component :important-property>. In the script for the child component, import it using camel-case: props: ['importantProperty']. Also in the methods of the child component, access the property using this.importantProperty.

So, for your code:

ParentComponent.vue

<parent-component>
    <child-component :computed-project="computedProject"></child-component>
</parent-component>

ChildComponent.vue:

<template></template>
<script type="text/javascript">
  ...
  props: ['computedProject'],
  methods: {
    importantMethod: function importantMethod() {
      // here we have computedProject
      console.log(this.computedProject);
    }
  }
  ...
</script>

yeah so i’ve changed my computed property to be called computedProjects. i’m passing it through like this in my child component.

<section class="project-list" :computed-Projects="computedProjects" >

then accessing like this

 created : function () {
       console.log(this.computedProjects)
        },

i’m guessing the data is just not loading from the api before the child component is loading?

cheers!

Since created only fires once, if the api call hasn’t returned by the time your component loads, it will not be available in the function.

I have solved a similar problem by making the child component dependent on the computedProject property using a v-if in the parent template.

<div v-if="computedProject"> 
  <router-view :computed-project="computedProject"></router-view>
</div>

What I did to solve this issue. I created an element

and gave it an ID, id=resp,.set the style=display: none; Inside the .then() block after the promise is returned with the response i accessed the element by it’s ID and set the inner HTML to the promise’s response, (response) => document.getElemntById(“resp”).innerHTML = response.data;. I then created a button and set the click listener to call a function which read the contents (innerHTML) of the element with that ID (document.getElementById(“resp”) and set it to this.data. Once it’s in data you can do what you want with it.
    <p id="resp" style="display:none;"></p>
    <button @click="setResponse">Set Response</button>
</div>
methods: {
    fetch() {
        axios.post(uri, formData)
        .then(function(response) {  document.getElementById("resp").innerHTML = response.data }).catch(function(error) {...})
    },
    setResponse() {
        this.$data.foo = document.getElementById("resp").innerHTML;
    }
},
data() {
    return {
        foo: ''
    }
}

You can always try a different event listener to call the method setResponse(). A button click was sufficient for me.

A better solution in addition to the solution I gave above.
Create a function that executes the axios post request separately and returns the response to the function fetch(). let this new func be called execute().
Then create a variable obj (const obj = this;) before instantiating execute() and pass obj into execute() along with formData to be sent in request. have the promise return an array with the response and obj inside an array. ( execute(formData, obj) { await axios.post(uri, formData) afterwards return [response, obj] ). Now in the fetch func, inside the promise (.then() ) you can use obj from the returned array to access this.data.

methods: {
    fetch() {
        const obj = this;
        this.execute(formData, obj);
        .then(function([response, obj]) {
            obj.$data.foo = response.data;
        }
    },
    async execute(formData, obj) {
        const uri = 'http:/.........';
        let res = await axios.post(uri, formData);
        return [res, obj];
    }
},
data() {
    return {
        foo: ' '
    }
}

now the response is saved inside data(). No need for the event listener (button @click) i used in the last reply i gave.