Hi everyone here is the mixin code I wrote as I want to use this for default.vue and error.vue layout. I am trying to avoid duplicating code in two layout.
export default {
provide () {
return {
copyRight: this.getCopyrightText,
email: this.getEmail,
socials: this.getSocials
}
},
computed: {
getMenu () {
return this.store.getters['general/getMenu'].menu
},
getSocials () {
return this.store.getters['general/getSocialDetails']
},
getCopyrightText () {
return this.store.getters['general/getCopyRight']
},
getEmail () {
return this.store.getters['general/getEmail']
}
},
middleware: 'load-menu-items'
}
This is what I get: Cannot read property 'length' of undefined
What am I doing wrong?
In your component I assume you're using .length on the data you're receiving from the getter method, which is probably where the error occurs.
First of all you should debug to see if your getter is actually working as expected. Try this and look at output in console for every getter computed property. If undefined is printed to the console you'll get the error you posted if you're using .length on it
getEmail () {
let data = this.store.getters['general/getEmail'];
console.log(data);
return data;
}
If you post the component which is using this mixin maybe I can help you further.
Related
I am building a web app with nuxt.
here's simplified code:
pages/index.vue
data() {
return {
item: {name:'', department: '', testField: '',},
}
}
async asyncData() {
const result = call some API
const dataToInitialize = {
name: result.username,
department: result.department,
testField: //want to assign computed value
}
return {item: dataToInitialize}
}
Inside asyncData, I call API and assign value to dataToInitialize.
dataToInitialize has testField field, and I want to assign some computed value based on username and department.
(for example, 'a' if name starts with 'a' and department is 'management'..etc there's more complicated logic in real scenario)
I have tried to use computed property , but I realized that asyncData cannnot access computed.
Does anyone know how to solve this?
Any help would be appreciated!
=======
not sure if it's right way, but I solved the issue by setting 'testfield' inside created.
created() {
this.item.testField = this.someMethod(this.item);
},
Looking at the Nuxt lifecyle, you can see that asyncData is called before even a Vue instance is mounted on your page.
Meanwhile, fetch() hook is called after. This is non-blocking but more flexible in a lot of ways.
An alternative using fetch() would look like this
<script>
export default {
data() {
return {
staticVariable: 'google',
}
},
async fetch() {
await this.$axios(this.computedVariable)
},
computed: {
computedVariable() {
return `www.${this.staticVariable}.com`
},
},
}
</script>
Another alternative, would be to use URL query string or params, thanks to Vue-router and use those to build your API call (in an asyncData hook).
Here is an example on how to achieve this: https://stackoverflow.com/a/68112290/8816585
EDIT after comment question
You can totally use a computed inside of a fetch() hook indeed. Here is an example on how to achieve this
<script>
export default {
data() {
return {
test: 'test',
}
},
async fetch() {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${this.nice}`)
console.log(await response.json())
},
computed: {
nice() {
return this.test + 'wow!'
},
},
}
</script>
I found that destructuring fetch({}) causes issues with accessing this inside fetch scope ->
async fetch({ store, $anyOtherGlobalVar }){
store.dispatch...
// destructuring approach changes the scope of the function and `this` does not have access to data, computed and e.t.c
}
If you want to access this scope for example this.data, avoid destructuring and access everything through this.
async fetch() {
this.$store...
this.data...
}
Code below yields error "Cannot read property 'form' of undefined":
computed: {
boundary_limits () {
return this.$refs.hemoBoundaryLimits.form;
},
high_risk_alerts () {
return this.$refs.highRiskAlerts.form;
},
alerts () {
return {
boundary_limits: this.boundary_limits,
high_risk_alerts: this.high_risk_alerts
}
}
}
Yet if I removed alerts(), I get no error and I can even console log boundary_limits or high_risk_alerts
successfully, which means $refs.hemoBoundaryLimits and this.$refs.highRiskAlerts are defined.
So Vue.js has a problem with how I define alerts, but I see no problem in it.
Any clue what's going on?
The error comes because you are trying to access $refs in computed property.
Computed properties are evaluated before the template is mounted and thus the hemoBoundaryLimits is undefined.
You should access $refs in the mounted hook.
As solution, you can trick it by knowing when the component is mounted using #hook event:
<hemoBoundaryLimits #hook:mounted="isHemoBoundaryLimitsMounted = true" />
And in the script
data: () => ({
isHemoBoundaryLimitsMounted: false
}),
computed: {
boundary_limits () {
if (!this.isHemoBoundaryLimitsMounted) return
return this.$refs.hemoBoundaryLimits.form;
}
}
In my parent component:
<UsersList :current-room="current_room" />
In the child component:
export default {
props: {
currentRoom: Object
},
data () {
return {
users: []
}
},
mounted () {
this.$nextTick( async () => {
console.log(this.currentRoom) // this, weirdly, has the data I expect, and id is set to 1
let url = `${process.env.VUE_APP_API_URL}/chat_room/${this.currentRoom.id}/users`
console.log(url) // the result: /api/chat_room/undefined/users
let response = await this.axios.get(url)
this.users = response.data
})
},
}
When I look at the page using vue-devtools, I can see the data appears:
I've run into this issue in the past – as have many others. For whatever reason, you can't rely on props being available in the component's mounted handler. I think it has to do with the point at which mounted() is called within Vue's lifecycle.
I solved my problem by watching the prop and moving my logic from mounted to the watch handler. In your case, you could watch the currentRoom property, and make your api call in the handler:
export default {
props: {
currentRoom: Object
},
data() {
return {
users: []
}
},
watch: {
currentRoom(room) {
this.$nextTick(async() => {
let url = `${process.env.VUE_APP_API_URL}/chat_room/${room.id}/users`
let response = await this.axios.get(url)
this.users = response.data
})
}
},
}
I don't think you really need to use $nextTick() here, but I left it as you had it. You could try taking that out to simplify the code.
By the way, the reason console.log(this.currentRoom); shows you the room ID is because when you pass an object to console.log(), it binds to that object until it is read. So even though the room ID is not available when console.log() is called, it becomes available before you see the result in the console.
I am using the modules mode of the store and in my projects.js inside my store folder I have:
export const getters = {
loadedProjects(state) {
return state.loadedProjects;
}
}
now in my computed how should I call it?
I’m trying like that:
computed: {
loadedProjects() {
return this.$store.getters.projects.loadedProjects;
},
}
but I get this error:
Cannot read property ‘loadedProjects’ of undefined
I had the same problem, if you are using the modules mode you can call your getters like that (in your case): this.$store.getters['projects/loadedProjects'];
So try to change your computed like that:
computed: {
loadedProjects() {
return this.$store.getters['projects/loadedProjects'];
},
}
You have to call your getter like this:
loadedProjects() {
return this.$store.getters['projects/loadedProjects'];
}
$store.getters['moduleName/getterName']
I am calling inside the computed an action from the store to run it and after I am returning a getter, this will create a loop.
The HTML
{{loadedProjects}}
The computed
computed: {
loadedProjects() {
this.$store.dispatch("getProjects");
return this.$store.getters.loadedProjects;
}
}
The store
import Vuex from "vuex";
import axios from "axios";
const createStore = () => {
return new Vuex.Store({
state: {
loadedProjects: []
},
mutations: {
setProjects(state, projects) {
state.loadedProjects = projects
}
},
actions: {
getProjects(vuexContext) {
console.log("hello1")
return axios.get("THE API URL")
.then(res => {
console.log("hello2")
vuexContext.commit("setProjects", res.data);
})
.catch(e => console.log(e));
}
},
getters: {
loadedProjects(state) {
return state.loadedProjects;
}
}
});
};
export default createStore;
I expect to call my action to populate my state and after to return my state to render my data.
What is the point of using the store action that makes an API call inside the computed property ... maybe you want to trigger loadedProjects change ? ....computed property is not asynchronous so either way the return line will be executed before the you get the response... you might try vue-async-computed plugin OR just use the call on the created hook like you have done which is the better way and you don't have to use a computed property you can just {{ $store.getters.loadedProjects }} on your template
Computed properties should not have side effects (e.g. calling a store action, changing data, and so on). Otherwise it can happen that the triggered side effect could lead to a re-rendering of the component and possible re-fetching of the computed property. Thus, an infinite loop
I changed the code like that:
created: function () {
this.$store.dispatch("getProjects")
},
computed: {
loadedProjects() {
return this.$store.getters.loadedProjects
}
}
It is working now but I would like to know but I have that problem working inside the computed and also I wonder if it's the best solution. Any help????