Vue getter property undefined - vue.js

i have state pages obtained from api
const state = () => ({
info: [],
pages: []
})
and Getter
const getters = {
// HomePage_Getters
HomeAdvantages(state) {
return state.pages.find((page) => (page.template = 'home')).home_advantages
}
}
On the vue Page
<div>
{{ HomeAdvantages }}
</div>
...
computed: {
...mapGetters({ HomeAdvantages: 'modules/general/HomeAdvantages' })
},
On Page refresh i get
Cannot read property 'home_advantages' of undefined
But in the state pages property is present
How i could solve this?
I will be grateful, for help

If pages property is empty, then state.pages.find((page) => (page.template = 'home')) may return undefined.
And in your code you want to get home_advantages from the find result.
Your code should look like this:
var page = state.pages.find((page) => (page.template == 'home'));
if (!page) {
return null;
}
return page.home_advantages

Related

How to define an empty Vuex state and then check it is not empty/null in Vue component?

I have a Vuex store which defines a state as null:
const state = {
Product: null // I could do Product:[] but that causes a nested array of data
};
const getters = {
getProduct: (state) => state.Product
};
const actions = {
loadProduct({commit}, {ProudctID}) {
axios.get(`/api/${ProductID}`).then(response => {commit('setProduct', response.data)})
.catch(function (error) {
//error handler here
}
}
};
const mutations = {
setProduct(state, ProductData) {
state.Product = ProductData
}
};
In my Vue component I want to display the Product data when it is available. So I did this:
<template>
<div v-if="Product.length">{{Product}}</div>
</template>
When I run it, I get an error stating
Vue warn: Error in render: "TypeError: Cannot read property 'length'
of null"
Okay, so I tried this which then does nothing at all (throws no errors and never displays the data):
<template>
<div v-if="Product != null && Product.length">{{Product}}</div>
</template>
What is the correct way to display the Product object data if it is not null? Product is a JSON object populated with data from a database which is like this:
[{ "ProductID": 123, "ProductTitle": "Xbox One X Gaming" }]
My Vue component gets the data like this:
computed:
{
Product() {
return this.$store.getters.getProduct
}
}
,
serverPrefetch() {
return this.getProduct();
},
mounted() {
if (this.Product != null || !this.Product.length) {
this.getProduct();
}
},
methods: {
getProduct() {
return this.$store.dispatch('loadProduct')
}
}
If I look in Vue Dev Tools, it turns out that Product in my Vue component is always null but in the Vuex tab it is populated with data. Weird?
This is a classic case to use computed:
computed: {
product() {
return this.Product || [];
}
}
In the store function when you do the request you can check
examoe
const actions = {
loadProduct({commit}, {ProudctID}) {
if (this.product.length > 0) {// <-- new code
return this.product // <-- new code
} else { // <-- new code
// do the http request
axios.get(`/api/${ProductID}`)
.then(response => {
commit('setProduct', response.data)}
)
.catch(function (error) {
//error handler here
}
}// <-- new code
}
};

Vue computed property not responding to state change

I cannot figure out why the details computed property in the following component is not updating when the fetch() method is called:
<template>
<div>
{{ haveData }} //remains undefined
</div>
</template>
<script>
export default {
props: {
group: {
type: Object,
required: true
},
},
computed: {
currentGroup() {
return this.$store.getters['user/navbar_menu_app_current_group'](
this.group.id
)
/*-- which is the following function
navbar_menu_app_current_group: state => item => {
return state.menu.find(m => {
return m.id == item
})
}
*/
/*-- this function returns an object like so
{
id: 1,
label: 'kity cats',
}
***details --> IS NOT DEFINED. If I add it to the current group as null, my problem goes away. However, this is a previous API call that does not set the `details` parameter.
*/
},
details() {
let c = this.currentGroup.details
console.log(c) // returns undefined, which makes sense, but it should be updated after this.fetch() is called
return c
},
haveData() {
return this.details != null
}
},
methods: {
async fetch() {
await this.$store.dispatch(
'user/navbar_menu_app_details_get',
this.group.id
)
//This is setting the "details" part of the state on menu which is referred to in the computed properties above
//Previous to this there is no state "this.group.details"
//If I add a console log to the mutation the action calls, I get what is expected.
}
},
created() {
if (!this.haveData) {
this.fetch()
}
}
}
</script>
If I change the array items to include details, it works:
{
id: 1,
label: 'kity cats',
details: null // <-- added
}
The unfortunate part is that the array is created from a large API call, and adding the details seems unnecessary, as it may never be needed.
How can I get the computed properties to work without adding the details:null to the default state?
Attempt 1:
// Vuex mutation
navbar_menu_app_details_set(state, vals) {
let app = state.menu.find(item => {
return item.id == vals[0] //-> The group id passing in the dispatch function
})
//option 1 = doesn't work
app = { app, details: vals[1] } //-> vals[1] = the details fetched from the action (dispatch)
//option 2 = doesnt work
app.details = vals[1]
//option 3 = working but want to avoid using Vue.set()
import Vue from 'vue' //Done outside the actual function
Vue.set( app, 'details', vals[1])
},
Attempt 2:
// Vuex action
navbar_menu_app_details_get(context, id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('navbar_menu_app_details_set', [
context.getters.navbar_menu_app_current(id), //-> the same as find function in the mutation above
apps[id]
])
resolve()
}, 1000)
})
}
// --> mutation doesn't work
navbar_menu_app_details_set(state, vals) {
vals[0].details = vals[1]
},
The Vue instance is available from a Vuex mutation via this._vm, and you could use vm.$set() (equivalent to Vue.set()) to add details to the menu item:
navbar_menu_app_details_set(state, vals) {
let app = state.menu.find(item => {
return item.id == vals[0]
})
this._vm.$set(app, 'details', vals[1])
},
All Objects in Vue are reactive and are designed in a way such that only when the object is re-assigned, the change will be captured and change detection will happen.
Such that, in your case, following should do fine.
app = { ...app, details: vals[1] }

Use tableTop.js to return an array that can be used in Vue Components

I am attempting to build an array of objects from a spreadsheet using tableTop.js that can be passed into other functions and vue components. I have been unsuccessful in returning anything I can actually use. I found this post that got me close to what I am after however what it is returning is an array of arrays of objects with two undefined array items beginning with [ob: Observer]
If I log out data in the getLibrary() function I can see the correct array how I need to receive it in my component.
If I don't push the data into the gData array in libraryData I receive undefined in vue from the function. I have attempted promises, normal functions etc. but nothing seems to work. Very appreciative of any help anyone can provide thanks.
Image 1 is what I am logging out in library data that I am trying to receive in vue.
Image 2 is what I am getting in vue
libraryData.js
// let gData = []
export default async function () {
let spreadSheet = 'url'
Tabletop.init({
key: spreadSheet,
callback: (data, tabletop) => { return getLibraryData(data,m tabletop) },
simpleSheet: true
})
}
export function getLibraryData(data, tabletop) {
// gData.push(data);
///gData = data
log(data)
// I just want to return the data here to be used in vue
return data;
}
index.js
import Vue from 'vue'
import libraryData from './partials/libraryData.js'
// Too be added into a vue-lodaer?
new Vue({
el: '#vhsLibrary',
router,
template: '<vhsLibrary/>',
})
window.addEventListener('DOMContentLoaded', () => {
libraryData()
})
vue_component.vue
<script>
import { getLibraryData } from '../../js/partials/library_data';
export default {
data: () => {
return {
gData: null
}
},
mounted () {
this.gData = getLibraryData()
log('Get Library', getLibraryData())
}
}
</script>
There's a few issues here:
You use async, but you never await. In your case, we want to await the resolution or rejection of a Promise:
export default async function () {
return await new Promise((resolve, reject) => {
const spreadSheet = 'url'
Tabletop.init({
key: spreadSheet,
callback: (data, tabletop) => { resolve({data, tabletop}) },
simpleSheet: true
})
})
}
There's no reason for the additional function because it has no gains. Let's look at Vue now.
First, your gData variable is initialized as null as opposed to []. Let's change that:
data () {
return {
gData: []
}
},
Next, let's update our mounted method. We can use the same async/await pattern here:
async mounted () {
const { data } = await getLibraryData()
this.gData = data
}
And now you can v-for="(row, index) in gData" to iterate it.
Here's a codepen for you, too

Computed Getter causes maximum stack size error

I'm trying to implement the following logic in Nuxt:
Ask user for an ID.
Retrieve a URL that is associated with that ID from an external API
Store the ID/URL (an appointment) in Vuex
Display to the user the rendered URL for their entered ID in an iFrame (retrieved from the Vuex store)
The issue I'm currently stuck with is that the getUrl getter method in the store is called repeatedly until the maximum call stack is exceeded and I can't work out why. It's only called from the computed function in the page, so this implies that the computed function is also being called repeatedly but, again, I can't figure out why.
In my Vuex store index.js I have:
export const state = () => ({
appointments: {}
})
export const mutations = {
SET_APPT: (state, appointment) => {
state.appointments[appointment.id] = appointment.url
}
}
export const actions = {
async setAppointment ({ commit, state }, id) {
try {
let result = await axios.get('https://externalAPI/' + id, {
method: 'GET',
protocol: 'http'
})
return commit('SET_APPT', result.data)
} catch (err) {
console.error(err)
}
}
}
export const getters = {
getUrl: (state, param) => {
return state.appointments[param]
}
}
In my page component I have:
<template>
<div>
<section class="container">
<iframe :src="url"></iframe>
</section>
</div>
</template>
<script>
export default {
computed: {
url: function (){
let url = this.$store.getters['getUrl'](this.$route.params.id)
return url;
}
}
</script>
The setAppointments action is called from a separate component in the page that asks the user for the ID via an onSubmit method:
data() {
return {
appointment: this.appointment ? { ...this.appointment } : {
id: '',
url: '',
},
error: false
}
},
methods: {
onSubmit() {
if(!this.appointment.id){
this.error = true;
}
else{
this.error = false;
this.$store.dispatch("setAppointment", this.appointment.id);
this.$router.push("/search/"+this.appointment.id);
}
}
I'm not 100% sure what was causing the multiple calls. However, as advised in the comments, I've now implemented a selectedAppointment object that I keep up-to-date
I've also created a separate mutation for updating the selectedAppointment object as the user requests different URLs so, if a URL has already been retrieved, I can use this mutation to just switch the selected one.
SET_APPT: (state, appointment) => {
state.appointments = state.appointments ? state.appointments : {}
state.selectedAppointment = appointment.url
state.appointments = { ...state.appointments, [appointment.appointmentNumber]: appointment.url }
},
SET_SELECTED_APPT: (state, appointment) => {
state.selectedAppointment = appointment.url
}
Then the getUrl getter (changed its name to just url) simply looks like:
export const getters = {
url: (state) => {
return state.selectedAppointment
}
}
Thanks for your help guys.

How to access to the vue store in the asyncData function of nuxt

in a component i want to acces to the store with the asyncData function like so :
asyncData({ app, params }) {
var url = `https://myapi/news/${app.$store.state.market}/detail/${params.id}`;
return app.$axios.get(url).then(response => {
return { actu: response.data };
});
}
but i received "Cannot read property 'state' of undefined"
is there another to receive the state of the store here ?
You need to get store from context. Reference
asyncData({ app, params, store }) {
var url = `https://myapi/news/${store.state.market}/detail/${params.id}`;
return app.$axios.get(url).then(response => {
return { actu: response.data };
});
This worked for me
Store/index.js
...
state: {
loadedPages: []
}
...
Page
async asyncData(context) {
...
console.log(context.store.state.loadedPages)
...
}