How to resolve Cannot read properties of undefined error? - javascript

I have a this warning:
[Vue warn]: Error in render: "TypeError: Cannot read properties of undefined (reading 'nestedArray')"
What is the solution to this? This is my beforeCreate functions:
beforeCreate() {
this.$store.dispatch("loadCities").then((response) => {
this.cities = response;
this.sortingCities=this.cities.slice(0).sort(function(a,b) {
return a.row - b.row || a.col-b.col;
})
this.sortingCities.map(item => {
if (!this.nestedArray[item.row]) {
this.nestedArray[item.row] = [];
}
this.nestedArray[item.row][item.col] = item;
});
});
My data property:
data() {
return {
cities: [],
selectedCity: null,
sortingCities:[],
nestedArray:[],
};
},
I use this property:
<img :src="require(`../images/${this.nestedArray?.[row]?.[col].imageId}.png`)" alt="">

Inside beforeCreate you should not expect any data, methods or computed to be available. This is documented here (vue 3) and here (vue 2).
Move the contents of beforeCreated into mounted.
If you have anything in <template> depending on the fetched data, give it an appropriate v-if (e.g: v-if="nestedArray.length").

Root cause of the problem : Issue is in accessing the data object properties inside beforeCreate life cycle hook. This life cycle hook called immediately after the instance has been initialized, before processing data option.
Solution : You can put your logic inside mounted() hook instead of beforeCreate as it is called after the instance has been mounted.
Live Demo :
new Vue({
el: '#app',
data() {
return {
message: []
}
},
beforeCreate() {
this.message.push('beforeCreate hook called!'); // ❌
},
mounted() {
this.message.push('mounted hook called!'); // ✅
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<pre>{{ message }}</pre>
</div>

Accessing the vue data without vue is created? Strange, isn't it? Instead, you can access the data using created and after that lifeCycles. Here is a small sample for you.
new Vue({
el: "#app",
data() {
return {
nestedArrays: []
}
},
created() {
console.log(this.nestedArrays)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Related

Vue JS - Rendering getter before template

I'm getting TypeError "Cannot read property 'email' of undefined" because it seems that the template is rendering before the getter returns the value. The value is indeed undefined as it is initialised as undefined in the store. But after the template renders, that value does return something. Is there anyway I can have my getter render after the template?
my code:
<template>
<div>
<Success :title="'title name'"
:subtitle="`your email is ${schoolDetails.email}.`"
:button-text="'button text'"
:button-link="ROUTE_NAMES_HK_ADMIN.SCHOOL_DETAILS"/>
</div>
</template>
<script>
import {ROUTE_NAMES_HK_ADMIN} from "#/router/modules/hkAdmin";
import Success from "#/components/partials/Success";
import {GET_SCHOOL_BY_ID} from "#/store/manager/actions";
export default {
name: "SchoolCreateSuccess",
components: {Success},
data: () => ({
ROUTE_NAMES_HK_ADMIN
}),
computed: {
schoolDetails: function () {
return this.$store.getters.getSelectedSchool;
},
},
methods: {
getSchoolDetails: function (schoolId) {
this.$store.dispatch(GET_SCHOOL_BY_ID, schoolId);
}
},
created() {
this.getSchoolDetails(this.$route.params.id);
}
}
How about initializing the schoolDetails variable with dummy value to fulfil the error?
Then maybe you can use watch instead of computed to align tracking the schoolDetails variable with the stored data.
So, maybe something like this:
data: () => ({
ROUTE_NAMES_HK_ADMIN,
schoolDetails: {email: ''}
}),
// note: 'watch' track changes (no changes == the function will not be called)
watch: {
// watch the state, not the getter
'$store.state.selectedSchool': () => {
this.schoolDetails = this.$store.getters.getSelectedSchool;
return;
}
}

Pass object as prop on Vue

How do you go on passing objects as props on vue? I would imagine this would be a simple task but apparently not.
I have the following code on a .vue file:
<template>
<div id="scatter"></div>
</template>
<script>
export default {
props: {
data: {
type: Object,
default: () => ({}),
},
},
mounted() {
console.log(this.data);
},
};
</script>
On html I try to pass the data props as follows :
<component :data="{x:1}"></component>
When I try log it into the console the result is only an empty observer object.
I believe the problem is somewhere else in your code as passing an object as a prop is as simple as you imagine:
// register the component
Vue.component('component', {
props: {
data: {
type: Object
}
},
template: '<div>Data: {{data}}</div>',
mounted: function () {
console.log(this.data)
}
})
new Vue({
el: '#example'
})
HTML:
<div id="example">
<component :data="{x:1}"></component>
</div>
Here's a fiddle showing it in action:
https://jsfiddle.net/tk9omyae/
Edit: After my initial answer and creating a JsFiddle, I am not sure why the behavior you described is happening. It works when narrowed down to the use case:
<script>
export default {
props: {
ok: {
type: Object,
default: () => ({}),
},
data: {
type: Object,
default: () => ({})
}
}
},
mounted () {
console.log(this.data) // {x:1}
console.log(this.ok) // {x:1}
}
}
</script>
And the HTML:
<component :ok="{x:1}" :data="{x:1}"></component>
Here is a JsFiddle that demonstrates the behavior: https://jsfiddle.net/mqkpocjh/
v-bind="yourObject"
Should do on
<my-component v-bind="yourObject"><my-component>
Not sure about <component></component>. Still digging into Vue. Try and let us know.
100% Working
Passing object is very simple way from one component to another component.
Child component Code simple code where StokDetail is an object passing from
other component
export default {
props: {
StockDetail: {
type: Object,
default: (()=>{})
},
},
created:function(){
console.log(this.StockDetail);
}
}
</script> ```
> Pass from Parent Component
>
<stock-detail-component v-bind:stock-detail="model.StockDetail"></stock-detail-component>
HTML attributes are case-insensitive, so when using non-string templates, camelCased prop names need to use their kebab-case (hyphen-delimited) equivalents:
such as StockDetail = stock-detail
you can see in this below snapshot
[1]: https://i.stack.imgur.com/tIofC.png

Error in render function: “TypeError: Cannot read property of undefined”, using Vuex and Vue Router

I have been dealing with an issue using Vue, Vuex and Vue-Router. I'm building a flash cards app, fetching all the cards on main app creation, then using a Vuex getter to get each card by its id which is passed as a route parameter.
Relevant bits:
App.vue
export default {
components: {
'app-header': header,
},
data() {
return {
}
},
created() {
this.$store.dispatch('getAllCards');
}
}
The dispatch('getAllCards') is just pulling all the cards from the DB and committing to Vuex store.js.
Now I set up a getter:
getters: {
cardById: (state) => (id) => {
return state.allCards.find((card) => card._id === id);
}
}
Here is Card.vue:
<template>
<div>
<br>
<div v-if="flipped" class="container">
<div class="box">
<pre v-if="card.code"><code class="preserve-ws">{{card.back}}</code></pre>
<p class="preserve-ws center-vertical" v-else>{{card.back}}</p>
</div>
</div>
<div v-else class="container">
<div class="box">
<h1 class="title has-text-centered center-vertical is-2">{{card.front}}</h1>
</div>
</div>
</template>
<script>
export default {
data() {
return {
card: {},
flipped: false,
general_card: false,
code_card: true,
random_card: false
}
},
computed: {
},
methods: {
},
created() {
this.card = this.$store.getters.cardById(this.$route.params.id);
}
}
</script>
I am getting the TypeError referenced in the title. My understanding is that the created() hook happens after data() has been set up, so then I can assign {card} using the getter. Unfortunately this displays nothing...
If I assign card() as a computed property:
computed: {
card() {
return this.$store.getters.cardById(this.$route.params.id);
}
}
The card shows, but I still get that error in console. Any idea why? I looked at this and attempted that solution, but to no avail.
The question don't have all the premise to get a correct answer.
(We don't know the mutations, the state, neither the actions and we have no clues about app-header component)
So we have to admit some hypothesis :
state.allCards is an empty Array when you mount the component
action getAllCards is an async function to retrieve or set state.allCards with a mutation
cardById's getters, return a function, so vuex can't apply reactive on a function. So if the state change, the getter won't be trigger correctly.
To correct this, use computed as you mention here.
But it don't fix the undefined error, that from my point of view, is because the getters return undefined on mount.
getters: {
cardById: (state) => (id) => {
var card = state.allCards.find((card) => card._id === id);
if(card) {
return card;
}
// on undefined return a default value
return {
front:'default value'
};
}
}
You can see the implementation on this jsfiddle with no error on console.
Or you can have a loading state on undefined value for your card component.
If my hypothesis is wrong please provide a jsfiddle to help you.
What you need is a derived state based on store state which is to return a filtered card based on a card id. This id is received to your component via the route params. So its better you use a computed property instead of passing arguments to the store getters
Instead of initializing card in data property make card a computed property like this:
computed:{
card(){
return this.$store.state.allCards.find((card) => card._id === this.$route.params.id);
}
}
Note this
If a component needs derived store state based on its own state(in your case rourte params), it should define a local computed property
I tried everyone else's solutions and they did not work. But I got it to work finally. Here is what worked for me:
I ended up including a top-level:
<div v-if="!card"> Loading card... </div>
<div v-else> Rest of card template </div>
That seems to have silenced the error. Also, the card lives as a computed property:
card() {
return this.$store.getters.cardById(this.$route.params.id);
}
I think it was throw error in this step
getters: {
cardById: (state) => (id) => {
return state.allCards.find((card) => card._id === id);
}
}
in this step, it can not find _id of cars, because allcards was null;
and then you use computed instead, when allcards has been change, it will get again;
you can change code like this
getters: {
cardById: (state) => (id) => {
return state.allCards.find((card) => card && card._id === id);
}
}
$route.params.id must be string, so what is the type of card._id? It seems each card._id is number, I think.

Vuex how to Access state data on component when it's mounted?

I'm trying to create a Quill.js editor instance once component is loaded using mounted() hook. However, I need to set the Quill's content using Quill.setContents() on the same mounted() hook with the data I received from vuex.store.state .
My trouble here is that the component returns empty value for the state data whenever I try to access it, irrespective of being on mounted() or created() hooks. I have tried with getters and computed properties too. Nothing seems to work.
I have included my entry.js file, concatenated all the components to make things simpler for you to help me.
Vue.component('test', {
template:
`
<div>
<ul>
<li v-for="note in this.$store.state.notes">
{{ note.title }}
</li>
</ul>
{{ localnote }}
<div id="testDiv"></div>
</div>
`,
props: ['localnote'],
data() {
return {
localScopeNote: this.localnote,
}
},
created() {
this.$store.dispatch('fetchNotes')
},
mounted() {
// Dispatch action from store
var quill = new Quill('#testDiv', {
theme: 'snow'
});
// quill.setContents(JSON.parse(this.localnote.body));
},
methods: {
setLocalCurrentNote(note) {
console.log(note.title)
return this.note = note;
}
}
});
const store = new Vuex.Store({
state: {
message: "",
notes: [],
currentNote: {}
},
mutations: {
setNotes(state,data) {
state.notes = data;
// state.currentNote = state.notes[1];
},
setCurrentNote(state,note) {
state.currentNote = note;
}
},
actions: {
fetchNotes(context) {
axios.get('http://localhost/centaur/public/api/notes?notebook_id=1')
.then( function(res) {
context.commit('setNotes', res.data);
context.commit('setCurrentNote', res.data[0]);
});
}
},
getters: {
getCurrentNote(state) {
return state.currentNote;
}
}
});
const app = new Vue({
store
}).$mount('#app');
And here is the index.html file where I'm rendering the component:
<div id="app">
<h1>Test</h1>
<test :localnote="$store.state.currentNote"></test>
</div>
Btw, I have tried the props option as last resort. However, it didn't help me in anyway. Sorry if this question is too long. Thank you for taking your time to read this. Have a nice day ;)
I will recommend the following steps to debug the above code:
In the Vue dev tools, check if the states are getting set after the network call
As you are trying to fetch data asynchronously, there can be a chance that data has not arrived when created/mounted hook is called.
Add an updated hook into your component and try to log or access the state and you should be able to see it.
Please provide the results from the above debugging, then I'll be able to add more details.

Setting props for components using v-for in Vue JS

I have a PhoneCard.vue component that I'm trying to pass props to.
<template>
<div class='phone-number-card'>
<div class='number-card-header'>
<h4 class="number-card-header-text">{{ cardData.phone_number }}</h4>
<span class="number-card-subheader">
{{ cardData.username }}
</span>
</div>
</div>
</template>
<script>
export default {
props: ['userData'],
components: {
},
data() {
return {
cardData: {}
}
},
methods: {
setCardData() {
this.cardData = this.userData;
console.log(this.cardData);
}
},
watch: {
userData() {
this.setCardData();
}
}
}
The component receives a property of userData, which is then being set to the cardData property of the component.
I have another Vue.js component that I'm using as a page. On this page I'm making an AJAX call to an api to get a list of numbers and users.
import PhoneCard from './../../global/PhoneCard.vue';
export default {
components: {
'phone-card': PhoneCard
},
data() {
return {
phoneNumbers: [],
}
},
methods: {
fetchActiveNumbers() {
console.log('fetch active num');
axios.get('/api').then(res => {
this.phoneNumbers = res.data;
}).catch(err => {
console.log(err.response.data);
})
}
},
mounted() {
this.fetchActiveNumbers();
}
}
Then once I've set the response data from the ajax call equal to the phoneNumbers property.
After this comes the issue, I try to iterate through each number in the phoneNumber array and bind the value for the current number being iterated through to the Card's component, like so:
<phone-card v-for="number in phoneNumbers" :user-data="number"></phone-card>
However this leads to errors in dev tools such as property username is undefined, error rendering component, cannot read property split of undefined.
I've tried other ways to do this but they all seem to cause the same error. any ideas on how to properly bind props of a component to the current iteration object of a vue-for loop?
Try
export default {
props: ['userData'],
data() {
return {
cardData: this.userData
}
}
}
Answered my own question, after some tinkering.
instead of calling a function to set the data in the watch function, all I had to do was this to get it working.
mounted() {
this.cardData = this.userData;
}
weird, I've used the watch method to listen for changes to the props of components before and it's worked flawlessly but I guess there's something different going on here. Any insight on what's different or why it works like this would be cool!

Categories

Resources