Troubleshooting vue extends failure - vue.js

I have a pretty simple mixin that doesn't seem to be working using nuxt. It's so basic I'm at my whits end trying to figure out the bug. My component is part of a fairly complicated form composition scheme, but the mixin is super simple.
script from my component simplified
import { VInput } from 'vuetify'
import FieldMask from '~/utils/FieldMask'
import CognitoField from '~/mixins/cognitoField'
export default {
name: 'CognitoBaseField',
extends: [VInput, CognitoField],
props: {
inlineLabels: { default: true },
noIndependantSubmission: Boolean,
groupEdit: false,
cognitoName: '',
displayValue: '',
fieldValue: '',
placeHolder: { defalut: 'enter text' },
label: '',
masker: { type: Function, default: FieldMask },
items: [],
selected: {},
itemText: '',
itemValue: '',
showForm: Boolean,
disableEnterKeySubmission: Boolean,
},
data() {
return {
value: '',
formActive: false,
}
},
methods: {
onCancelClick() {
this.value = ''
this.formActive = false
console.log('test mixin')
console.log(this.thisIsATest())
},
},
}
My mixin - verbatim
export default {
computed: {
testComp() {
return 'working'
},
},
methods: {
thisIsATest() {
return 'working'
},
},
}
The result here is I trigger onCancelClick on the component and the page crashes with
TypeError: this.thisIsATest is not a function
To really include a usable version of the component in this question I'd need to share a number of components so its probably not called for. Unless somebody can obviously see a glaring error, I guess the real question is how to troubleshoot this further?

This should do it
<script>
import CognitoField from '~/mixins/cognitoField'
export default {
name: 'CognitoBaseField',
mixins: [CognitoField],
}
</script>
Regarding the documentation, this pattern is no longer recommended. The usage of Composables is better on several aspects.

Related

Data not being passed from Child Data to Parent Props

I have a Request Form Component, and within this request form Component I have a Dropdown Menu Component, which I will link both below. All values in my table are pushed into an object upon hitting the Submit Button. However my dropdown selection is only being picked up by my console.log and not being pushed into the Object.
I'm not so familiar with Vue, so I'm not sure what direction to go in for fixing this. I'll attach the relevant (?) pieces of code below.
Parent Component:
<SelectComponent :selected="this.selected" #change="updateSelectedValue" />
export default {
fullScreen: true,
name: 'CcRequestForm',
mixins: [BaseForm],
name: "App",
components: {
SelectComponent,
},
data() {
return {
selected: "A",
};
},
props: {
modelName: {
default: 'CcRequest',
},
parentId: {
type: Number,
default: null,
},
},
mounted() {
this.formFields.requester.value = this.currentRequesterSlug;
},
destroyed() {
if (!this.modelId) return;
let request = this.currentCcRequest;
request.params = request.params.filter(p => p.id)
},
computed: {
...mapGetters(['ccTypesForRequests', 'currentRequesterSlug', 'currentCcRequest']),
ccTypesCollection() {
return this.ccTypesForRequests.map((x)=>[x.slug, this.t(`cc_types.${x.slug}`)]);
}
},
methods: {
addParam() {
this.addFormFields(['params'], {
slug: '',
name: '',
isRequired: true,
description: '',
typeSlug: '',
selected: ''
});
},
deleteParam(idx){
this.removeFormFields(['params', idx]);
},
restoreParam(idx){
this.restoreFormFields(['params', idx])
},
$newObject() {
return {
slug: '',
name: '',
isAbstract: false,
requester: '',
description: '',
status: 'inactive',
params: [],
selected: ''
};
},
$extraPrams() {
return {
parentId: this.parentId,
};
},
updateSelectedValue: function (newValue) {
this.selected = newValue;
},
},
watch: {
selected: function (val) {
console.log("value changed", val);
},
},
};
Child Component:
<script>
export default {
name: "SelectComponent",
props: {
selected: String,
},
computed: {
mutableItem: {
get: function () {
return this.selected;
},
set: function (newValue) {
this.$emit("change", newValue);
},
},
},
};
You have to define the emit property in the parent component, or else it won't know what to expect. That would look like:
<SelectComponent :selected="this.selected" #update-selected-value="updateSelectedValue" />
Check out this tutorial for more information: https://www.telerik.com/blogs/how-to-emit-data-in-vue-beyond-the-vuejs-documentation
To update selected property inside the object, in this constellation, you need to update object property manually upon receiving an event, inside of updateSelectedValue method. Other way could be creating a computed property, since it's reactive, wrapping "selected" property.
computed: {
selectedValue () {
return this.selected
}
}
And inside of object, use selectedValue instead of selected:
return {
...
selected: selectedValue
}

Accessing validation property outside the current scope in vuelidate/VueJS?

Given the following component in Vue 2 using vuelidate from property validation:
<template>
// template stuff here...
</template>
<script>
import { validationMixin } from "vuelidate";
import { required, requiredIf } from "vuelidate/lib/validators";
export default {
name: "ParentComponent",
mixins: [validationMixin],
data() {
return {
selected: false,
user: {
email: null,
password: null,
},
};
},
validations: {
selected: { required },
user: {
email: { required: requiredIf((vm) => vm.selected) },
password: { required: requiredIf((vm) => vm.selected) },
},
},
};
</script>
The properties user.email and user.password are required, but only if the validation for selected passes successfully. However, this approach does not seem to work, probably because selected is not part of the user validation object.
Is there a way to access this validation property? Something like this...
validations: {
selected: { required },
user: {
email: { required: requiredIf((vm) => vm.$parentVm.selected) },
password: { required: requiredIf((vm) => vm.$parentVm.selected) },
},
},
Just like that. Demo https://codesandbox.io/s/green-darkness-90fjs.
requiredIf good solution for resolve this problem

Vue Cannot read property '$refs' of undefined

I have a Vue application that leverages Vuetify. In this application I have a component called city-selector.vue which is setup like this:
<template>
<select-comp
:id="id"
:items="cityList"
:item-text="name"
:item-value="cityCode"
#input="onInput">
</select-comp>
</template>
<script>
import VSelect from '../vuetify/VSelect';
export default {
name: 'city-select-comp',
extends: VSelect,
props: {
id: {
type: String,
default: '',
},
cityList: {
type: Array,
default: () => { return [] }
},
},
methods: {
onInput() {
//Nothing special, just $emit'ing the event to the parent
},
},
}
</script>
Everything with this component works fine except that when I open my dev tools I get a bunch of console errors all saying this (or something similar to this):
Cannot read property '$refs' of undefined
How can I fix this sea of red?
This is due to a bad import that you do not need. Remove the import VSelect and the extends statements and your console errors will disappear, like this:
<script>
export default {
name: 'city-select-comp',
props: {
id: {
type: String,
default: '',
},
cityList: {
type: Array,
default: () => { return [] }
},
},
methods: {
onInput() {
//Nothing special, just $emit'ing the event to the parent
},
},
}
</script>

Vue Js Components does not get destroyed?

I'm currently working on an very versatile dashboard to display various data.
For the frontend I'm using the latest nuxt and vue version.
My dashboard has many kinds of variations to display data (for example pie charts, line charts,...) these are described in components which are called dynamically.
The Problem is that when I browse from the Page "/" to another (for example "/foo") the interval gets fired again and crashes the app.
That happenes after the lifecycle hook destroyed. I tried to define the interval as an variable and stop it in the beforeDestroy hook but it did not help.
let interval= setInterval(this.fetchData.bind(null, configuration, connector), configuration.refreshTime)
/* later */
clearInterval(interval);
Do you see an error?
Thank you.
Thats the relevant code:
Template
<no-ssr>
<v-container grid-list-md>
<v-layout row wrap v-masonry transition-duration="0.5s" item-selector=".flex" column-width="#grid-sizer">
<v-flex xs1 sm1 md1 lg1 x1 id="grid-sizer"></v-flex>
<component :key="dashboardItem.id" v-for="(dashboardItem,index) in dashboardItems" :is="dashboardItem.type" :connector="dashboardItem.connector"
:type="dashboardItem.type" :configuration="dashboardItem.configuration" :id="dashboardItem.id" :index="index"></component>
</v-layout>
</v-container>
</no-ssr>
Script
import OnlyValue from '#/components/dashboardItems/OnlyValue.vue'
import TrafficLight from '#/components/dashboardItems/TrafficLight.vue'
import ChartAllHover from '#/components/dashboardItems/ChartAllHover.vue'
import PieChart from '#/components/dashboardItems/PieChart.vue'
import Section from '#/components/dashboardItems/Section.vue'
import Gauge from '#/components/dashboardItems/Gauge.vue'...
export default {
name: 'HomePage',
head () {
return {
title: "Dashboard"
}
},
computed: {
...mapGetters({
isAuthenticated: "users/isAuthentificated",
profileName: "profiles/name",
dashboardItems: "profiles/dashboardItems"
})
},
mounted() {
if (typeof this.$redrawVueMasonry === 'function') {
this.$redrawVueMasonry()
}
},
components: {
OnlyValue,
TrafficLight,
ChartAllHover,
PieChart,
Section,
Gauge
}
}
When calling a components it looks the following:
import dashboardItem from '~/mixins/dashboardItem'
export default {
name: "gauge",
mixins: [dashboardItem],
props: {
connector: {
type: String,
required: true
},
type: {
type: String,
required: true
},
configuration: {
type: Object,
required: true
},
id: {
type: Number,
required: true
},
index: {
type: Number,
required: true
}
},
data: () => ({
initOptions: {
renderer: 'svg'
},
options: {
tooltip: {
formatter: "{c}%"
},
series: [{
name: null,
type: 'gauge',
detail: {
formatter: '{value}%'
},
data: null
}]
},
isLoading: true
}),
methods: {
getData(configuration, connector) {
this.fetchData(configuration, connector)
setInterval(this.fetchData.bind(null, configuration, connector), configuration.refreshTime)
},
fetchData(configuration, connector) {
this.getSingleValue(configuration, connector)
.then(data => {
this.isLoading = false
let percent = (data.value / configuration.max) * 100
percent = Math.round(percent * 10) / 10
this.$nextTick(function () {
this.$refs.gauge.mergeOptions({
series: [{
name: data.title,
data: [{
value: percent,
name: data.title
}]
}]
})
})
this.$redrawVueMasonry()
})
.catch(e => console.log(e))
}
},
mounted () {
this.getData(this.configuration, this.connector)
}
}

How to map a nested Vuelidate validation object with computed properties in VueJS?

I have a tabs container with multiple forms. Some of the fields in all forms have some complex logic that I didn't want to repeat on each form, so I created a custom component that is used in all forms. I'm trying to use Vuelidate to validate all of my forms but since those field names are the same, and of course have the same validation logic, the Vuelidate validation object is the same in all forms, meaning, if I fill in the email field in formA, then all forms with that same field will also validate correctly, even though the rest of the forms haven't been filled in at all.
I tried to wrap my validations inside an object named as the forms, and this seems to separate all validation logic correctly, but, I have other setup there that prevents me from using data attributes and I use computed attributes instead. As far as I know, the validations object must match the way we access fields data, like, data() { formA: { email } } would match to validation object validations: { formA: { email } }, the problem is, since I'm not using data properties, I don't know how to map computed properties.
This is what I have:
export default {
components: { PhoneField, TopNote, SubmitButton, NameFieldsGroup, EmailField },
validations: {
formA: {
firstName: { required },
lastName: { required },
email: {
required,
email
},
phone: {
required,
length: minLength(10)
}
}
},
created() {
this.$store.commit('setFormValidation', this.$v);
},
data() {
return {}
},
computed: {
firstName: function() {
return this.$store.getters.formState.firstName;
},
lastName: function() {
return this.$store.getters.formState.lastName;
},
email: function() {
return this.$store.getters.formState.email;
},
phone: function() {
return this.$store.getters.formState.phone;
}
}
};
I've been messing around with this for the past several days, but can't figure it out. Anyone can suggest a solution for this?
Figured it out. Not sure why it works but it does now. The fix is to use Vuex's mapState like this:
import { mapState } from 'vuex';
export default {
components: { PhoneField, TopNote, SubmitButton, NameFieldsGroup, EmailField },
validations: {
formA: {
firstName: { required },
lastName: { required },
email: {
required,
email
},
phone: {
required,
length: minLength(10)
}
}
},
created() {
this.$store.commit('setFormValidation', this.$v);
},
data() {
return {}
},
computed: {
...mapState(['formA']),
firstName: function() {
return this.$store.getters.formState.firstName;
},
lastName: function() {
return this.$store.getters.formState.lastName;
},
email: function() {
return this.$store.getters.formState.email;
},
phone: function() {
return this.$store.getters.formState.phone;
}
}
};