Make API call before computed properties run Vuejs - vue.js

I am creating a vuejs application that need to fetch data from the backend using API. And the data will be populated using a computed properties and display it on the screen.
But I found that the computed properties will run before the data is fetched from the backend. This make the computed properties always return me an error. (Data not exists at the time computed properties is running)
Here is the example of my code.
<template>
<!-- Populated data will be display here -->
<div v-for="service in serviceList" :key="service"></div>
</template>
<script>
import { getServices } from "#/services/product.js";
export default {
name: "Services",
data() {
return {
services: null,
};
},
methods: {
async fetchService() {
try {
const response = await getServices();
this.services = response.data;
} catch (error) {
console.log(error);
}
},
},
computed: {
serviceList() {
// services will be populated here
return this.services;
},
},
created() {
// Featch data from backend
this.fetchService();
},
};
</script>
Is there any ways to make my data ready before the computed properties is running?
Thank you.

Based on the current code snippet there's no need to a computed property, just init the services with an empty array :
data() {
return {
services: [],
};
},
then use it in template :
<div v-for="service in services" :key="service"></div>
You can format the response directly inside try block.
try {
const response = await getServices();
// format your response data here and then bind in the `this.services`
this.services = response.data;
}

Related

Provide/inject axios response

I have one component in which I use axios to get data from API, it works and I can use this data in this component, but when I try to provide this data to another component, I dont get any data there.
Here is part of my code:
data() {
return {
theme: [],
};
},
provide() {
return {
theme: this.theme
}
},
methods: {
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme = response.data.data));
},
},
mounted() {
this.getTheme();
},
and this is the second component:
<template>
<div class="project-wrapper">
<project-card
v-for="course in theme.courses"
:name="course.name"
:key="course.id"
></project-card>
</div>
</template>
<script>
import ProjectCard from "../components/ProjectCard.vue";
export default {
inject: ["theme"],
components: {
ProjectCard,
}
};
</script>
What is wrong with my code?
Second option in the link may help you
provide() {
return {
$theme: () => this.theme,
}
},
and
inject: ["$theme"],
computed: {
computedProperty() {
return this.$theme()
}
}
and
v-for="course in computedProperty.courses"
When you set provide to 'handle' theme it adds reactivity to the value of theme - i.e the empty array ([]).
If you modify the elements in this array, it will remain reactive - however if you replace the array then the reactivity is broken.
Instead of overwriting theme in the axios call, try adding the resulting data to it. For example:
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme.push(...response.data.data));
}
You are passing theme to the child component as injected property.
See Vue.js Docs:
The provide and inject bindings are NOT reactive. This is intentional.
However, if you pass down an observed object, properties on that
object do remain reactive.
As inject bindings are not reactive, the changed value of theme will not be visible from inside of the child component (it will stay the same as if no axios call happened).
Solution 1
Pass the value to the child component as an observed object. It means that in in your getTheme() method you will not rewrite the whole property value (this.theme = ...) but only write into the object which is already stored in the property (this.theme.themeData = ...).
data() {
return {
theme: { },
};
},
provide() {
return {
theme: this.theme
}
},
methods: {
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme.themeData = response.data.data));
},
},
mounted() {
this.getTheme();
}
Solution 2
Alternatively you can pass the value to the child component using classical props which are always reactive.

Computed properties are not reacting to state change

I am working on a product overview page, that send out an API-call based on the current Category you are looking at:
store.dispatch("tweakwise/fetchAPIAttributesLayeredNavigation", {
tweakwiseCategory,
this.pageNumber,
}
In my Store, the data from this API-call will be set in the following VueX Store State:
this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes: []
I want to react to this data in my front-end but my Computed methods do not seem to react to this change. As you can also see in the function below I added a Catch to prevent a "Non defined" error. The function, however, will not be called after the state has been set.
This computed property is also added to the Mount() op the component
computed: {
initialFetchProducts() {
this.fetchProducts(
this.$store.state.tweakwise?.tweakwiseLayeredNavigationAttributes || []
);
},
},
make computed property for state you want to watch,
than create watch() for this prop. In watch you can react on computed property change.
<template>
<div v-for="product in products"></div>
</template>
<script>
export default {
data: {
return {
products: [],
}
},
computed: {
tweakwiseLayeredNavigationAttributes() {
return this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes;
},
},
watch: {
// on every tweakwiseLayeredNavigationAttributes change we call fetchProducts
tweakwiseLayeredNavigationAttributes: {
handler(newValue, oldValue) {
this.fetchProducts(newValue);
},
deep: true, // necessary for watching Arrays, Object
immediate: true, // will be fired like inside mounted()
}
},
methods: {
async fetchProducts(params) {
const products = await axios.get('/api', params);
this.products = products;
}
}
};
</script>

vue apexcharts: charts in component rendered directly without animation after get axios data from props

In the parent template, I call a component and pass the axios data:
// template
<myChart
:pdata="axiosData"
/>
// script
export default {
data() {
return {
axiosData,
}
}
created() {
axios.get('some-url')
.then(response => {
this.axiosData = response.data
})
}
}
Because the child component "myChart" rendered an empty chart before it receives the props data, so I updated the chart series in a watch:
// below code is implemented in component
// template
<vue-apex-charts
type="donut"
height="200"
:series="mySeries"
:options="myOptions"
/>
// script
export default {
props: {
pdata: {
type: Array,
default: () => [],
},
},
data() {
return {
mySeries: [],
myOptions: ...
}
},
watch: {
pdata(val) {
this.mySeries= val
},
}
}
The data in chart is rendered without any animation - if I set a static series in data(), chart is rendered with animation.
I tried some API like ApexCharts.render(), ApexCharts.updateOptions(..), ApexCharts.updateSeries(..), but they all can't trigger the data animation after props data updated in watch.
So what is the best practice in such scenario? How to trigger the animation after get axios data from server...
Thanks in advance.
Finally figured out the solution ...
Adding v-if="pdata && pdata.length" can trigger the animation after props data receviced.

Vue.js, Vuex; component view not reacting when data in the Vuex store is mutated (with mysterious exception)

This is my first foray into using Vuex, and I'm baffled by a problem relating to a searchResults array, managed in the $store, specifically why a SearchResults view component doesn't seem to be reacting when the store is mutated.
I have a search form component which, when submitted, invokes a search function (mixin), dispatches an action which updates the searchResults array in the store, and then loads ViewSearchResults.vue, where the search results are displayed - and this is working.
Now, ViewSearchResults.vue also includes the search form component, and when a subsequent search is run, again the search function runs successfully, the store is updated accordingly, however ViewSearchResults.vue is not reacting to the change in the store, e.g., update lifecycle doesn't fire, so the new search results are unavailable
... and then in my debugging journey I discovered that by adding a reference to the store in the template - e.g., {{ this.$store.state.searchResults.length }}, the view updates, the new data is available, and any subsequent searches successfully update the view.
None of my experience with Vue.js so far explains this. Can someone please shed some light on this, and how I can realize the desired results without polluting my markup?.
Many thanks in advance.
relevant excerpt of my search mixin:
export default {
created: function() {},
methods: {
doSearch: function(term) {
const searchTerm = term.toLowerCase();
this.$store.dispatch("setSearchTerm", term);
let searchResults = [];
// SNIP: search (iterate) a bunch of .json data ...
searchResults.push(searchResult); // searchResults array CONFIRMED √
this.$store.dispatch("setSearchResults", searchResults);
}
}
}
relevant excerpt of the store:
export default new Vuex.Store({
strict: true,
state: {
searchTerm: "",
searchResults: [],
},
mutations: {
setSearchTerm(state, payload) {
state.searchTerm = payload;
},
setSearchResults(state, payload) {
console.log(payload); // √ confirmed: updated array is there
state.searchResults = payload;
console.log(state.searchResults); // √ confirmed: updated array is there
}
},
getters: {
},
actions: {
// dispatched in the search mixin
setSearchTerm(context, payload){
context.commit("setSearchTerm", payload);
},
setSearchResults(context, payload) {
context.commit("setSearchResults", payload);
}
},
modules: {
}
})
... and ViewSearchResults.vue (relevant excerpts):
// IF I LEAVE THIS IN, BOB'S YOUR UNCLE ... WITHOUT IT, THE VIEW DOESN'T REACT
<div style="display: none;">this.$store.state.searchResults: {{ this.$store.state.searchResults.length }}</div>
<ul class="search-results">
<li v-for="(imgObj, ix) in searchResults" :key="ix">
<img :src="require('#/assets/img/collections/' + imgObj.path + '/' + imgObj.data + '/' + imgObj.imgFile)" alt="" />
</li>
</ul>
export default {
components: {
// 'app-search' occurs elswhere in the app, but when submitted, loads this ViewSearchResults, search component still present
'app-search': SearchForm
},
props: {
},
data() {
return {
searchTerm: "",
searchResults: []
}
},
created: function() {
// only becuz refresh
if (!this.searchTerm) {
this.searchTerm = this.$route.params.searchTerm;
}
console.log(this.$store.state.searchResults.length); // 0 if refreshed, ERGO:
this.$store.state.searchResults.length ? this.searchResults = this.$store.state.searchResults : this.searchResults = JSON.parse(localStorage.getItem("searchResults"));
console.log(this.searchResults); // searchResults √
},
updated: function() {
// ?!?!?! WHY DOES THIS FIRE ONLY IF I LEAVE THE REFERENCE TO THE STORE IN THE TEMPLATE? {{ this.$store.state.searchResults.length }}
this.$store.state.searchTerm ? this.searchTerm = this.$store.state.searchTerm : this.searchTerm = localStorage.getItem("searchTerm");
this.$store.state.searchResults.length ? this.searchResults = this.$store.state.searchResults : this.searchResults = JSON.parse(localStorage.getItem("searchResults"));
},
computed: {
},
mounted: function() {},
mixins: [ Search ]
}
Many thanks again for any insight.
Whiskey T.
You've got nothing updating in your component so it won't need to execute the update hook.
It seems you actually want your component to be driven by the values in the store.
I would set it up as recommended in the Vuex guide
computed: {
searchResults () {
return this.$store.state.searchResults
}
},
created () {
this.doSearch(this.$route.params.searchTerm)
}
You could also use the mapState helper if you wanted.
computed: mapState(['searchResults']),
The part where you load data from localstorage should be done in your store's state initialiser, ie
let initialSearchResults
try {
initialSearchResults = JSON.parse(localStorage.getItem('searchResults'))
} catch (e) {
console.warn('Could not parse saved search results')
initialSearchResults = []
}
export default new Vuex.Store({
strict: true,
state: {
searchTerm: "",
searchResults: initialSearchResults
},

VueJs Data Passed From Root to Child Component via Prop Results in only an observable object

I have an app which calls a web service in the created() function and populates a property of the root data object. The property is passed via a prop to a child component and using the Chrome dev tools I can see that the prop data is available on the child component.
The problem I have is that I try to set data properties in the child component using values passed via the prop I end up with undefined property data. If I use the Chrome inspection tools and add a breakpoint I can see that the prop is an observable object in the form of {__ob__: Observer} and as such, I cannot directly access any of the data. My suspicion is that the child object sets it's data properties before the web service call has completed in the root.
How can I overcome this?
I've created a JsFiddle for this:
https://jsfiddle.net/ProNotion/a8c6nqsg/
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {
customerEmail: this.customer_data.customerEmail1
}
}
}
});
new Vue({
el: "#app",
data() {
return {
customer: {}
};
},
methods: {
init() {
var self = this;
axios.get("https://0bb1313e-b089-432e-b6bc-250f6162d7f0.mock.pstmn.io/GetCustomerData")
.then(response => {
self.customer = response.data;
}).catch(response => {
console.error(response);
});
}
},
created() {
this.init();
}
});
Here is my HTML markup:
<div id="app">
<mycomponent :customer_data="customer" />
</div>
<script type="x-template" id="my-component-template">
<div>
<p>{{form_data.customerEmail1}}</p>
</div>
</script>
Check response data type and format
console.log(typeof response.data) // string
{ "customerEmail1": "me#example.com", } // Remove `,`
You must parse to JSON type
axios.get(...).then(response => {
self.customer = JSON.parse(response.data.replace(',', ''))
})
Set property to watch with `deep` option
[Deep watching](https://v2.vuejs.org/v2/api/#vm-watch) will be detect nested value changes inside Objects
```
Vue.component("mycomponent", {
template: '#my-component-template',
props: ["customer_data"],
data() {
return {
form_data: {}
}
},
watch: {
customer_data: {
handler (val) {
this.form_data = val;
},
deep: true
}
}
});
```
Demo: https://jsfiddle.net/ghlee/f4gewvqn