Vue 2 : Cannot read property id of null - vue.js

I'm having a list of orders, where each of them is clickable to view the orderdetails.
I have used hours of debugging to try understanding why the id property is null at the landing page, but looks fine through the store, and api call functions.
The best way to describe this is through code, so take a look (See the comments where the ID still has a valid value).
-> WORKING ROUTE
{ path: '/vieworder/:id', component: ViewOrder, props: true },
-> ROUTE PUSH TO DETAILS PAGE :
methods: {
...mapActions(['getOrders']),
init() {
this.getOrders()
},
viewOrder: function(order){
this.$router.push('/vieworder/' + order.id)
}
},
-> VIEWORDER.VUE (ID IS NULL IN TEMPLATE..)
import * as types from '../store/mutationtypes'
import { mapGetters, mapActions } from 'vuex'
export default {
name: 'ViewOrder',
props: {
id: {
type: String,
required: true
}
},
created() {
this.$store.dispatch('getOrderById', {
id: this.id
})
console.log('The id is now : ' + this.id)
},
computed: {
...mapGetters(["order"])
}
}
<template>
<div class="col-sm-10 col-sm-offset-1">
<h4>{{ order.id }}</h4>
</div>
</template>
--> STORE AND GETORDERBYID (ID VALUE IS CORRECT HERE...)
export default new Vuex.Store({
state: {
orders: [],
order: null
},
getters: {
orders: state => state.orders,
order: state => state.order
},
actions: {
getOrders({ commit }) {
api.getOrders().then(orders => commit(types.UPDATE_ORDERS, orders))
},
getOrderById({ commit }, { id }){
console.log("In Store now - Do we have an id ? : " + id)
api.getOrderById(id).then(order => commit(types.UPDATE_ORDER, order))
},
etc.etc.etc...
--> API CALL RETURNING DATA
getOrderById(orderId){
return axios.get('http://localhost:3000/Orders/' + orderId)
.then(response => response.data)
},
--> EXAMPLE DATA RETURNED FROM API CALL
"Orders": [
{
"id": 123456,
"PaidDate": "2017-01-12",
"AppId": "KLM-UXI-NIX-FIX",
"TicketType": "Barn - Enkeltbillett",
"TicketCount": 1,
"OrderSum": "17",
"MediaChannel": "Android",
"StatusCode": "08-12-34-56-78",
"PaymentOption": "VISA"
},
--> ERROR

This isn't an answer to OP's specific situation, but to the title of the question. I had the same console error message from vuejs "Cannot read property id of null" or "Cannot read property id of undefined".
I eventually solved it by going through all my code and making sure every usage of "x.id" is inside a block like "if (x)". For example, if "x" is set in "props" of the vue component, it looks like the following can fail with "Cannot read property id of undefined":
<div v-for="y in x" :key="y.id">
{{ y.name }}
</div>
The solution was to wrap it in a v-if block like this:
<div v-if="x">
<div v-for="y in x" :key="y.id">
{{ y.name }}
</div>
</div>
Similarly, in a computed property, it looks like the following can fail:
computed: {
sumids: function () {
var finalsum = 0
for (var y of this.x) {
finalsum += y.id
}
return finalsum
}
},
The solution was to wrap it in an if block like this:
computed: {
sumids: function () {
var finalsum = 0
if (this.x) {
for (var y of this.x) {
finalsum += y.id
}
}
return finalsum
}
},

You can try to change getOrderById({ commit }, { id }) to getOrderById({ commit }, id) in vuex and call it as this.$store.dispatch('getOrderById', id) in mounted() hook, if the order is empty.
Also you can check if the order exists like this <div class="col-sm-10 col-sm-offset-1" v-if="order.id"> to avoid errors before data is fetched.

Related

Vuex: How can I pass a variable to a getter?

In a vue app I get an elementId from the URL to pass in to a vuex getter:
<template>
<p>elementId: {{ elementId }}</p>
<p>loadedElement: {{ loadedElement }}</p>
</template>
<script>
export default {
data() {
return {
elementId: this.$route.params.id,
};
},
computed: {
loadedElement() {
return this.$store.getters.getElementById(this.elementId);
},
},
};
</script>
Getter:
getElementById: (state) => (id) => {
return state.elements.find(ele => ele.id === id)
},
Right now the page gets rendered like this:
However, when hardcode an elementId, it works:
<template>
<p>elementId: {{ elementId }}</p>
<p>loadedElement: {{ loadedElement }}</p>
</template>
<script>
export default {
data() {
return {
elementId: 3,
};
},
computed: {
loadedElement() {
return this.$store.getters.getElementById(this.elementId);
},
},
};
</script>
I don't understand what I'm doing wrong since getting the elementId from the route seems to work, but it is not passed into the getter function.
What am I doing wrong?
Most likely this.$route.params.elementId is a string but your element IDs are numbers. Using === to compare a string and number will not match.
Try using == instead, or converting this.$route.params.elementId to a number:
data() {
return {
elementId: +this.$route.params.id,
};
},
(Of course, maybe you want to do more error checking as part of this.)

Replace tag dynamically returns the object instead of the contents

I'm building an chat client and I want to scan the messages for a specific tag, in this case [item:42]
I'm passing the messages one by one to the following component:
<script>
import ChatItem from './ChatItem'
export default {
props :[
'chat'
],
name: 'chat-parser',
data() {
return {
testData: []
}
},
methods : {
parseMessage(msg, createElement){
const regex = /(?:\[\[item:([0-9]+)\]\])+/gm;
let m;
while ((m = regex.exec(msg)) !== null) {
msg = msg.replace(m[0],
createElement(ChatItem, {
props : {
"id" : m[1],
},
}))
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
}
return msg
},
},
render(createElement) {
let user = "";
let msg = this.parseMessage(this.$props.chat.Message, createElement)
return createElement(
'div',
{
},
[
// "hello",// createElement("render function")
createElement('span', '['+ this.$props.chat.Time+'] '),
user,
msg,
]
)
}
};
</script>
I thought passing createElement to the parseMessage method would be a good idea, but it itsn't working properly as it replaces the tag with [object object]
The chatItem looks like this :
<template>
<div>
<span v-model="item">chatITem : {{ id }}</span>
</div>
</template>
<script>
export default {
data: function () {
return {
item : [],
}
},
props :['id'],
created() {
// this.getItem()
},
methods: {
getItem: function(){
obj.item = ["id" : "42", "name": "some name"]
},
},
}
</script>
Example :
if the message looks like this : what about [item:42] OR [item:24] both need to be replaced with the chatItem component
While you can do it using a render function that isn't really necessary if you just parse the text into a format that can be consumed by the template.
In this case I've kept the parser very primitive. It yields an array of values. If a value is a string then the template just dumps it out. If the value is a number it's assumed to be the number pulled out of [item:24] and passed to a <chat-item>. I've used a dummy version of <chat-item> that just outputs the number in a <strong> tag.
new Vue({
el: '#app',
components: {
ChatItem: {
props: ['id'],
template: '<strong>{{ id }}</strong>'
}
},
data () {
return {
text: 'Some text with [item:24] and [item:42]'
}
},
computed: {
richText () {
const text = this.text
// The parentheses ensure that split doesn't throw anything away
const re = /(\[item:\d+\])/g
// The filter gets rid of any empty strings
const parts = text.split(re).filter(item => item)
return parts.map(part => {
if (part.match(re)) {
// This just converts '[item:24]' to the number 24
return +part.slice(6, -1)
}
return part
})
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<template v-for="part in richText">
<chat-item v-if="typeof part === 'number'" :id="part"></chat-item>
<template v-else>{{ part }}</template>
</template>
</div>
If I were going to do it with a render function I'd do it pretty much the same way, just replacing the template with a render function.
If the text parsing requirements were a little more complicated then I wouldn't just return strings and numbers. Instead I'd use objects to describe each part. The core ideas remain the same though.

[Vue warn]: Property or method "names" is not defined on the instance but referenced during render

I'm setting up a Vue.js project and connecting it to Firebase for the real time database.
Problem: I am able to save the data to the Firebase database however I am not able to render it to the view.
Error Message:
[Vue warn]: Property or method "names" is not defined on the instance
but referenced during render.
I have tried to adjust the vue instance "names" property by adding it the data function instead of making it a separate property in the instance, but that is not working.
<div id="app">
<label for="">Name</label>
<input type="text" name="" id="" v-model="name">
<button #click="submitName()">Submit</button>
<div>
<ul>
<li v-for="personName of names"
v-bind:key="personName['.key']">
{{personName.name}}
</li>
</ul>
</div>
</div>
<script>
import {namesRef} from './firebase'
export default {
name: 'app',
data () {
return {
name: "levi",
}
},
firebase: {
names: namesRef
},
methods: {
submitName() {
namesRef.push( {name:this.name, edit:false} )
}
}
}
</script>
<style>
Expected Result: Data saved to Firebase is rendered on the view
Actual result:
[Vue warn]: Property or method "names" is not defined on the instance
but referenced during render. Make sure that this property is
reactive, either in the data option, or for class-based components, by
initializing the property.
Essentially, you have an incorrect attribute in your Vue instance.. You need to move firebase into data..
([CodePen])
I was unable to get this working in a Stack Snippet..
~~~THE FIX~~~
VUE/JS
firebase.initializeApp({
databaseURL: "https://UR-DATABASE.firebaseio.com",
projectId: "UR-DATABASE"
});
const database = firebase.database().ref("/users");
const vm = new Vue({
el: "#app",
data: {
firebase: {
names: []
},
name: "SomeName"
},
methods: {
getFirebaseUsers() {
this.firebase.names = [];
database.once("value", users => {
users.forEach(user => {
this.firebase.names.push({
name: user.child("name").val(),
id: user.child("id").val()
});
});
});
},
handleNameAdd() {
let id = this.generateId();
database.push({
name: this.name,
id: id
});
this.name = "";
this.getFirebaseUsers();
},
generateId() {
let dt = new Date().getTime();
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
let r = ((dt + Math.random() * 16) % 16) | 0;
dt = Math.floor(dt / 16);
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
});
}
},
mounted() {
this.getFirebaseUsers();
}
});
HTML
<script src="https://www.gstatic.com/firebasejs/6.1.1/firebase.js"> .
</script>
<div id="app">
<label for="">Name</label>
<input type="text" name="" id="" v-model="name">
<button #click="handleNameAdd">Submit</button>
<div>
<ul>
<li v-for="(person, index) in firebase.names"
v-bind:key="person.id">
{{person.name}} | {{person.id}}
</li>
</ul>
</div>
</div>
OLD ANSWER:
This is what it should look like inside of data:
...
data() {
firebase: {
names: [],
}
}
...
Therefore, the data in your v-for would be referenced via firebase.names like:
...
<li v-for="(personName, index) in firebase.names"
:key="index"> // <<-- INDEX IS NOT THE BEST WAY TO STORE KEYS BUT ITS BETTER THAN NOTHING
//:key="personName.id // <<-- YOU COULD ALSO DO SOMETHING LIKE THAT, IF YOU HAVE A UNIQUE ID PER PERSON
{{personName.name}}
</li>
...
OPTIMAL FIX:
You could use a computed property if you wanted to automatically save/retrieve data from firebase each time a user adds a new name...as outlined in the CodePen and Code Snippet..
THE ISSUE:
<script>
import {namesRef} from './firebase'
export default {
name: 'app',
data () {
return {
name: "levi",
}
},
firebase: { // <<--- THIS IS INVALID, AND WHY IT'S NOT RENDERING
names: namesRef // CHECK YOUR CONSOLE FOR ERRORS
},
methods: {
submitName() {
namesRef.push( {name:this.name, edit:false} )
}
}
}
</script>
Try this.
You need to return the object in data
<script>
import {namesRef} from './firebase'
export default {
name: 'app',
data () {
return {
name: "levi",
firebase: {
names: namesRef
},
}
},
methods: {
submitName() {
namesRef.push( {name:this.name, edit:false} )
}
}
}
</script>
<style>
Use a computed property for names. Computed is more appropriate than data in this case, mainly because the component does not own the data. If it eventually resided in a vuex store, for instance, it would then react to external changes.
<script>
import {namesRef} from './firebase'
export default {
name: 'app',
data () {
return {
name: "levi",
}
},
computed: {
names() {
return namesRef
}
}
methods: {
submitName() {
namesRef.push( {name:this.name, edit:false} )
}
}
}
</script>
Try this
<script>
import {namesRef} from './firebase'
export default {
name: 'app',
data () {
return {
name: "levi",
}
},
cumputed: {
names: namesRef
},
methods: {
submitName() {
namesRef.push( {name:this.name, edit:false} )
}
}
}
</script>
Got mine working.
The solution is pretty simple.
Add names:[] to data object so it looks like:
...
data () {
return {
name: "levi",
names:[]
}
},
....
That's pretty much it.
Explaination
The firebase object data needs to be defined in order to use it
If you have more issues check the vuex documentation replicate that to your code.

Expected array got function. Passing function into component vuejs

I am trying to pass a function into my component and I keep getting this error back. "Invalid prop: type check failed for prop "form_type". Expected Array, got Function." My function returns an array so I am a little lost on how to fix this.
The function I am referencing is selectedType & the component in question is ChildTab
<template>
<div class="row">
<q-field
label="Contact Type"
:labelWidth="3"
error-label="Please select a contact type"
:error="!!failed_validations.contact_type"
>
<q-select v-model="contact_type" :options="contact_types"/>
</q-field>
</div>
<ChildTabs
:form_type="selectedType"
/>
<q-field class="float-right">
<q-btn color="faded" v-on:click="goBack()">Cancel</q-btn>
<q-btn color="green-6" v-on:click="selectedType()">Submit</q-btn>
</q-field>
</div>
</div>
</template>
<script>
'use strict';
import ChildTabs from '../tabs';
export default {
name: 'contacts-view',
data: function () {
return {
contact_type: '',
contact_types: [
{
label: 'Pregnancy',
value: 'pregnancy',
form_type: [
'BreastFeeding',
'Pregnancy'
]
},
{
label: 'Post Partum (Includes Birth)',
value: 'postpartum',
form_type: [
'Birth',
'BreastFeeding',
'PostPartum'
]
},
{
label: '1 - 2 Month',
value: '1_2_months',
form_type: [
'BreastFeeding',
'DuoMonths'
]
},
{
label: '6 Month',
value: '6_months',
form_type: [
'SixMonth'
]
}
],
}
},
props: {
},
computed: {
selectedType: function ()
{
var values = this.contact_types.map(function(o) { return o.value });
var index = values.indexOf(this.contact_type);
this.selectedForms = this.contact_types[index].form_type
// console.log(this.selectedForms);
return this.selectedForms;
}
},
methods: {
},
created: function () {
this.selectedType();
},
components: {
ChildTabs
}
}
</script>
As you try to call selectedType on click "Submit", maybe you should call it as a method.
Inside selectedType you bind a selectedForms property. Why don't you just initialize this property inside data as an empty array and pass it as a props of your ChildTabs component ?
<template>
<div class="row">
<ChildTabs :form_type="selectedForms" />
</div>
</template>
export default {
name: 'contacts-view',
data: function () {
return {
selectedForms: [],
// ...
}
},
methods: {
selectedType() {
var values = this.contact_types.map(function(o) { return o.value });
var index = values.indexOf(this.contact_type);
this.selectedForms = this.contact_types[index].form_type
}
},
//...
}
Fiddle example
What you bind as a prop in a component goes as same in the component. So as you're referencing selectedType in your ChildTabs component - the method selectedType will be received by ChildTabs as a prop. So either you edit your propType in ChildTabs component and invoke that passed method as needed or you call the selectedType method on the fly when passed in as a prop like
<ChildTabs :form_type="selectedType()" />
This will call that method then and will bind the resulting array as prop

Vue JS prop in component always undefined

I have a <select> component. Once I select a category I need to get the ID and also a boolean which checks if the category has a subcategory, if it does, I make an API call to fetch the subcategories.
Parent template:
<material-selectcat v-model="catId" name="category" id="selcat">
<option
v-for="cat in cats"
:value="cat.cat_id"
:subcat="cat.has_subCat"
v-text="cat.category_name"
></option>
</material-selectcat>
Child component:
<template>
<select><slot></slot></select>
</template>
<script>
export default {
props: ['value', 'subcat'],
watch: {
watcher: function() {}
},
computed: {
watcher: function() {
this.relaod(this.value);
this.fetchSubCategories(this.value, this.subcat);
}
},
methods: {
relaod: function(value) {
var select = $(this.$el);
select.val(value || this.value);
select.material_select('destroy');
select.material_select();
},
fetchSubCategories: function(val, sub) {
var mdl = this;
var catID = val || this.value;
console.log("sub: " + sub);
mdl.$emit("reset-subcats");
if (catID) {
if (sub == 1) {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
} else {
axios.get(URL.API + '/subcategories/' + catID)
.then(function(response) {
response = response.data.subcatData;
response.unshift({
subcat_id: '0',
subcategory_name: 'All Subcategories'
});
mdl.$emit("update-subcats", response);
$('.subdropdown').fadeIn();
})
.catch(function(error) {
if (error.response.data) {
swal({
title: "Something went wrong",
text: "Please try again",
type: "error",
html: false
});
}
});
}
} else {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
}
}
},
mounted: function() {
var vm = this;
var select = $(this.$el);
select
.val(this.value)
.on('change', function() {
vm.$emit('input', this.value);
});
select.material_select();
},
updated: function() {
this.relaod();
},
destroyed: function() {
$(this.$el).material_select('destroy');
}
}
</script>
But inside the fetchSubCategories() function this line always returns undefined:
console.log("sub: " + sub);
If I check the Vue Devtools tab in my Chrome inspector, I can see that all of the data exists:
cat_id:0
category_name:"All Subcategories"
has_subCat:0
But why doesnt has_subCat get passed as a prop?
The subcat prop is undefined because you are not passing it to the component. But, a subcat prop doesn't make much sense anyway, since you want to check the value for each option in the select which could all be different.
If you compose the options inside of the component definition:
// child component script
props: ['value', 'options'],
// child component template
<template>
<select>
<slot>
<option
v-for="option in options"
:key="option.id"
:value="option.id"
:subcat="option.subcat"
v-text="option.text"
></option>
</slot>
</select>
</template>
And then pass a correctly formatted options prop to the component:
// parent component script
computed: {
options() {
return this.cats.map((cat) => {
return {
id: cat.cat_id,
subcat: cat.has_subcat,
text: cat.category_name
}
});
}
}
// parent component template
<material-selectcat v-model="catId" :options="options" name="category" id="selcat">
</material-selectcat>
Then, you could get the correct subcat value for any option's corresponding value by referencing the options prop in the fetchSubCategories method:
fetchSubCategories: function(val) {
var mdl = this;
var catID = val || this.value;
var sub = (this.options.find(o => o.id === val) || {}).subcat;
...
The reason your value prop is defined in your code is that you are using the v-model directive on the component tag. The v-model directive is just syntactic sugar for :value="something" #input="something = $event.target.value". Since you've specified catID as the v-model, that will be passed to your component as the value prop.