I'm doing a duplicate test on a user with vuelidate and I'm receiving a TypeError on a errors[0].$message which is in a p tag that is getting rendered only when there is an error
Here's the code
<template>
<v-container>
<v-row>
<v-col cols="12">
<label>Username</label>
</v-col>
<v-col cols="12">
<input type="text" v-model.lazy="username" placeholder="Username" #blur="v$.username.$touch">
<p v-if="v$.username.$error" class="red--text">{{v$.username.$errors[0].$message}}</p>
</v-col>
</v-row>
</v-container>
</template>
<script>
import axios from 'axios'
import useVulidate from '#vuelidate/core'
import {required, helpers, minLength} from '#vuelidate/validators'
const isUsernameTaken = (value) => axios.get('userunique', {params: {username: value}}).then(res => {return res.data})
export default{
setup(){
return {
v$: useVulidate()
}
},
data(){
return {
username: ''
}
},
validations(){
return{
username: {
required,
minLength: minLength(4),
isUserUnique: helpers.withMessage('Username taken', helpers.withAsync(isUsernameTaken))
}
}
}
}
</script>
And the Error
TypeError: Cannot read properties of undefined (reading '$message')
vue.runtime.esm.js:619 [Vue warn]: Error in render: "TypeError: Cannot read properties of undefined (reading '$message')"
Validation works fine but error in console is kind of buugging me any help would be great thanks.
This looks like a bug in Vuelidate's logic. The docs for $error state...
Equivalent to this.$dirty && !this.$pending && this.$invalid.
However in testing it doesn't appear to honour the $pending state.
This means that while your async validator is running, $error can be true and $errors can be empty.
A simple work-around for this is to use a different condition for rendering the error block...
<p v-if="v$.username.$errors.length" class="red--text">
{{ v$.username.$errors[0].$message }}
</p>
Issue raised with Vuelidate here ~ https://github.com/vuelidate/vuelidate/issues/1046
Related
I got the error as shown in the title. I read a bit about this error and most answers say that it's about referring to non-existing method/data properties or it's caused by a silly spelling. In the following chunk of code I refer to existing componentNames data property correctly. I've noticed, that this issue arises only when I use Object.keys() method (as to get to component names) on componentNames.
<template>
<v-container fluid>
<v-row dense>
<v-col
v-for="(comp, n) in componentNames"
:key="n"
:cols="n === 0 ? 2 : 10"
>
<v-card>
<component :is="comp"></component>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
import A from '../views/A.vue';
import B from '../views/B.vue';
export default {
name: 'Project',
data() {
return {
//componentNames: ['A', 'B'] // when I use this line of code then it works perfectly
componentNames: Object.keys(this.$options.components) // however, this line doesn't work
}
},
components: {
A,
B
},
};
</script>
Error:
[Vue warn]: Property or method "componentNames" is not defined on the
instance but referenced during render.
Try to init componentNames with an empty array then assign Object.keys($options.components) to it inside created hook:
data() {
return {
componentNames: []
}
},
created(){
this.componentNames=Object.keys(this.$options.components)
},
components: {
A,
B
},
When I change the value of my v-text-field, I can see from the Vue tab of the developer tools that the value of lastName is updated in the user store with my code below. But, for some reason, the values of my 3 tests in my template below are not updated (or synchronized). Why ? What's wrong in my code ?
pages/home.vue:
<template>
<v-container>
<v-container>
<v-row>
<v-col>
<v-text-field v-model="lastName" />
</v-col>
</v-row>
</v-container>
TEST 1:
{{ $store.state.user.last_name }}
TEST 2:
{{ lastName }}
TEST 3:
<v-text-field v-model="lastName" />
</v-container>
</template>
<script>
export default {
computed: {
lastName: {
get() {
return this.$store.state.user.last_name
},
set(value) {
this.$store.commit('user/UPDATE_PROP', ['last_name', value])
}
}
}
}
</script>
store/user.js
export const mutations = {
UPDATE_PROP(state, payload) {
state[payload[0]] = payload[1]
}
}
I think the problem is in the way you set up your store. According to the Nuxt documentation,
you create a store directory and
every .js file inside the store directory is transformed as a namespaced module (index being the root module).
Also, according to the documentation,
... your state value should always be a function to avoid unwanted shared state on the server side.
Your state should look something like this
store/user.js
export const state = () => ({
last_name: ''
})
export const mutations = {
UPDATE_PROP(state, payload) {
state[payload[0]] = payload[1]
}
}
I'm attempting to pass a boolean value from Vuex store as a prop into a component to control the visibility on an alert. I pass the prop to the alerts value property.
The result is the error:
[Vue warn]: Invalid prop: type check failed for prop "value". Expected Boolean, got String with value "globalAlert.alert".
I've adjusted how I pass the to the component a few different ways, removes parens, etc. But it appears to be taking my globalAlert as a literal String, even though I've validated that the Vuex getter is returning the globalAlert error properly.
Implementation
This store controls the global alert state
const state = {
globalAlert: {
alert: false,
alertTitle: "",
alertMessage: ""
}
}
const getters = {
getGlobalAlert: state => state.globalAlert
}
const actions = {
setGlobalAlert({ commit }, error) {
commit('setAlert', error)
}
}
const mutations = {
setAlert: (state, globalAlert) => (state.globalAlert = globalAlert)
}
export default {
state,
getters,
actions,
mutations
}
This is alert component with globalAlert prop
<template>
<div id="app">
<v-app id="inspire">
<div>
<v-alert
value="globalAlert.alert"
dismissible
color="red"
transition="slide-y-transition"
dark
dense
border="left"
elevation="2"
class="ma-5"
icon="error"
>
Something bad just happened
</v-alert>
<div class="text-center">
<v-btn v-if="!alert" dark #click="alert = true">Reset Alert</v-btn>
</div>
</div>
</v-app>
</div>
</template>
<script>
export default {
name: "GlobalAlert",
props: {
globalAlert: {}
}
};
</script>
This is the usage of the component, where globalAlert is pulled from the store and sent down to the component
...
<GlobalAlert
:globalAlert="globalAlert">
</GlobalAlert>
...
import { mapGetters, mapActions } from "vuex";
import GlobalAlert from "../components/GlobalAlert";
...
components: {GlobalAlert},
computed: {
...mapGetters(["isAuthenticated", "userDetails", "getGlobalError"]),
globalAlert(){
return this.$store.state.error.globalAlert;
}
}
I'm just starting to learn Vue and am on to validation.
I've found some older examples which use Vee-Validate but it seems to have changed recently. How can I convert this code to use the new version of Vee-Validate?
As far as I can tell, the code below is attempting to send a bespoke error message rather than the default to the screen if there is an error.
Chrome browser is telling me that it cannot read property 'first' of undefined, so I don't think I can access the error using this.errors.
Is it still possible to access the errors inside 'computed'?
<template>
<div>
<ValidationProvider rules="required" v-slot="{ errors }">
<input type="text" v-model="input" name="myfield">
<span>{{ myError }}</span>
</ValidationProvider>
</div>
</template>
<script>
import { ValidationProvider } from 'vee-validate';
export default {
components: {
ValidationProvider
},
computed: {
myError () {
if (this.errors.first('myfield') === 'The myfield field is required.') {
return 'My bespoke message'
}
return this.errors.first('myfield')
}
}
};
</script>
As answered by MartinT, ValidationProvider uses Scoped Slots and therefore you cannot access it directly in the parent component. However, you still have some options to achieve what you want:
Using Methods
You can pass the error array as an argument of a method and return the bespoke error message you want!
<template lang="html">
<validation-provider rules="required" v-slot="{ errors }">
<input type="text" v-model="input" name="myfield">
<span>{{ myError(errors) }}</span>
</validation-provider>
</template>
<script lang="js">
import { ValidationProvider } from 'vee-validate'
export default {
components: { ValidationProvider },
data () {
return {
input: null
}
},
methods: {
myError (errors) {
if (errors[0] === 'The myfield field is required.') {
return 'My bespoke message'
}
return errors[0]
}
}
}
</script>
But this isn't probably the best option.
Customizing Rules' Messages
In Vee-Validate 3.x.x you can easily customize the error message from an existing rule or you can even create a new rule.
import { extend } from 'vee-validate'
// overwriting the 'required' rule error message.
extend('required', {
...required
message: 'My bespoke message'
})
And then you can display the bespoke message.
<template lang="html">
<validation-provider rules="required" v-slot="{ errors }">
<input type="text" v-model="input" name="myfield">
<span>{{ errors[0] }}</span>
</validation-provider>
</template>
Have a look at Scoped slots. In a nutshell, ValidationProvider component is using a slot, which provides you with errors object. You can think of it as an internal object of ValidationProvider. However, the problem is, it can only be used inside of the slot scope (within ValidationProvider). Your usage assumes, that errors obj is part of your component instance (either data, computed, method...), which is not true. More reading can be found here.
I currently have a login form that is using vuetify and vuelidate. Vuetify is used to create the form and display error messages, whereas vuelidate is used to validate user input. My problem is that the error messages are not displayed in red. Below are my codes and a screenshot of the login form
Main.js
import Vue from 'vue'
import App from './App.vue'
import Vuelidate from 'vuelidate'
import router from './router'
import store from './store'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
Vue.use(Vuetify)
Vue.use(Vuelidate)
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Login.vue
<template>
<!-- v-container is used to center the contents -->
<v-container>
<v-form>
<v-flex xs12 sm10 md8 lg6>
<v-text-field
label="Full Name"
v-model.trim="fullName"
#input="$v.fullName.$touch()"
:counter="10"
:error-messages="fullNameErrors"
></v-text-field>
</v-flex>
<v-flex xs12 sm10 md8 lg6>
<v-text-field
label="Email"
v-model.trim="email"
#input="$v.email.$touch()"
:error-messages="emailErrors"
></v-text-field>
</v-flex>
<v-flex xs12 sm10 md8 lg6>
<v-text-field
label="Password"
v-model.trim="password"
#input="$v.password.$touch()"
:error-messages="passwordErrors"
></v-text-field>
</v-flex>
<v-btn #click="submit">submit</v-btn>
<v-btn #click="clear">clear</v-btn>
</v-form>
</v-container>
</template>
<script>
import { required, email, maxLength } from "vuelidate/lib/validators";
export default {
data() {
return {
fullName: "",
email: "",
password: ""
};
},
validations: {
email: {
required,
email
},
password: {
required
},
fullName: {
required,
maxLength: maxLength(10)
}
},
computed: {
fullNameErrors() {
const errors = [];
if (!this.$v.fullName.$dirty) return errors;
!this.$v.fullName.maxLength &&
errors.push("Name must be at most 10 characters long");
!this.$v.fullName.required && errors.push("Name is required.");
return errors;
},
emailErrors() {
const errors = [];
if (!this.$v.email.$dirty) return errors;
!this.$v.email.email && errors.push("Must be valid e-mail");
!this.$v.email.required && errors.push("E-mail is required");
return errors;
},
passwordErrors() {
const errors = [];
if (!this.$v.password.$dirty) return errors;
!this.$v.password.required && errors.push("Password is required");
return errors;
}
},
methods: {
submit() {
this.$v.$touch();
},
clear() {
this.$v.$reset();
this.fullName = "";
this.email = "";
this.password = "";
}
}
};
</script>
Looks like its missing some CSS and font.
I am using the stylus import with no issues.
npm install roboto-fontface
npm install vuetify
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/src/stylus/main.styl'
import 'roboto-fontface/css/roboto/roboto-fontface.css'
Vue.use(Vuetify);
Stylus requires extra npm plugins:
stylus
stylus-loader
vue-style-loader
Further debugging, ensure all of your projects components are
encapsulated in a layout with the v-app tag, standard vue project with an App.vue file expects v-app tag inside its template.
App.vue:
<template>
<v-app>
<login />
</v-app>
</template>
v-app produces the following wrapper div tags that are critical to styles being applied to content within:
<div data-app="true" class="application theme--light" id="app">
<div class="application--wrap">
code injects here
</div>
</div>