vuelidate async validator - how to debounce? - vue.js

So I have an issue with async validator on my email/user form element. Every time a letter is typed in, it checks for validation. If the email is 30chars, then that is over 30 calls! Anyone know the best way to debounce a vuelidate custom validator? When I try to use debounce, i get all sorts of errors from vuelidate since it's expecting a response.
<div class="form-group">
<label for="emailAddress">Email address</label>
<input type="email" class="form-control col-md-6 col-sm-12" v-bind:class="{ 'is-invalid': $v.user.email.$error }" id="emailAddress" v-model.trim="$v.user.email.$model" #change="delayTouch($v.user.email)" aria-describedby="emailHelp" placeholder="email#example.com">
<small v-if="!$v.user.email.$error" id="emailHelp" class="form-text text-muted">We'll never share your email with anyone.</small>
<div class="error invalid-feedback" v-if="!$v.user.email.required">Email address is required.</div>
<div class="error invalid-feedback" v-if="!$v.user.email.email">This is not a valid email address.</div>
<div class="error invalid-feedback" v-if="!$v.user.email.uniqueEmail">This email already has an account.</div>
</div>
<script>
import { required, sameAs, minLength, maxLength, email } from 'vuelidate/lib/validators'
import webapi from '../services/WebApiService'
import api from '../services/ApiService'
const touchMap = new WeakMap();
const uniqueEmail = async (value) => {
console.log(value);
if (value === '') return true;
return await api.get('/user/checkEmail/'+value)
.then((response) => {
console.log(response.data);
if (response.data.success !== undefined) {
console.log("Email already has an account");
return false;
}
return true;
});
}
export default {
name: "Register",
data() {
return {
errorMsg: '',
showError: false,
user: {
firstName: '',
lastName: '',
email: '',
password: '',
password2: ''
}
}
},
validations: {
user: {
firstName: {
required,
maxLength: maxLength(64)
},
lastName: {
required,
maxLength: maxLength(64)
},
email: {
required,
email,
uniqueEmail //Custom async validator
},
password: {
required,
minLength: minLength(6)
},
password2: {
sameAsPassword: sameAs('password')
}
}
},
methods: {
onSubmit(user) {
console.log(user);
/*deleted*/
},
delayTouch($v) {
console.log($v);
$v.$reset()
if (touchMap.has($v)) {
clearTimeout(touchMap.get($v))
}
touchMap.set($v, setTimeout($v.$touch, 1250))
}
}
}
</script>
When I try to wrap my async function with the debounce, vuelidate does not like it, so I removed it. Not sure how to limit the custom "uniqueEmail" validator.

as 'Teddy Markov' said, you could call $v.yourValidation.$touch() on your input blur event. I think it's more efficient way to use Vuelidate.
<input type="email" class="form-control col-md-6 col-sm-12"
:class="{ 'is-invalid': !$v.user.email.$anyError }"
id="emailAddress" v-model.trim="$v.user.email.$model"
#change="delayTouch($v.user.email)"
#blur="$v.user.email.$touch()"
aria-describedby="emailHelp"
placeholder="email#example.com"
>

I found the best way was to combine with regex for emails
if regex test passes then proceed to check if unique - else skip the check completely.

Related

how to stop form submitting if form is invalid in vue3 cli compostion apis in vee validate

i am new to vue3 and composition apis.
there is problem with validating a simple form.
i am trying to stop form submission if form is not valid or not touched as it is vaild by default if inputs not touched.
login.vue
<form #submit="onSubmit">
<div class="form-group">
<input
name="email"
type="text"
v-model="email"
/>
<span>{{ emailError }}</span>
</div>
<div class="form-group">
<input
name="password"
type="password"
v-model="password"
/>
<span>{{ passwordError }}</span>
</div>
<div class="login-buttons">
<button
type="submit"
>
{{ $t("login.login") }}
</button>
</div>
</form>
login.js
<script>
import { useForm, useField,useIsFormValid } from "vee-validate";
import * as yup from "yup";
export default {
name: "LoginPage",
setup() {
const {
errors,
handleSubmit,
validate,
} = useForm();
// Define a validation schema
const schema = yup.object({
email: yup
.string()
.required()
.email(),
password: yup
.string()
.required()
.min(8),
});
// Create a form context with the validation schema
useForm({
validationSchema: schema,
});
// No need to define rules for fields
const { value: email, errorMessage: emailError } = useField(
"email"
);
const { value: password, errorMessage: passwordError } = useField(
"password"
);
const onSubmit = handleSubmit(async () => {
const { valid, errors } = await validate();
if (valid.value === false) {
return;
} else {
const response = await http.post(APIs.login, data);
}
});
return {
email,
emailError,
password,
passwordError,
onSubmit
};
},
};
</script>
in handelSubmit function if (vaild.value === false) it should return and stop the logic but always the value for vaild is true so it continues the HTTP calling for the api.
only wan't to stop sending data to the if the form is invaild using composition apis
You created two forms with useForm and basically using the submit handler of the first one that doesn't define any rules.
Remove the second useForm call and pass the rules to the first one.
const schema = yup.object({
email: yup.string().required().email(),
password: yup.string().required().min(8),
});
const { errors, handleSubmit, validate } = useForm({
validationSchema: schema
});

Using SendGrid with Nuxt.js

This post is not a question actually.
More like help to the world for people who are struggling with Nuxt.js and SendGrid.
I've been using stackoverflow for such a long time so maybe now it's my turn to start helping others.
I've been working on Nuxt.js WebApp development for the past 8 weeks.
Nuxt.js is a massive learning curve and challenge for me but I really love working with this technology.
I spent the last 2 days developing sending the form with the use of SendGrid. There's not too much help online and I've been struggling a lot but I made it!
So maybe some people will find my post useful.
Here's my form:
<form
v-show="!isSubmitted"
class="contact-us__form"
#submit.prevent="validate">
<b-form-group :class="{'form-group--error': $v.name.$error}">
<b-form-input
id="name"
v-model.trim="$v.name.$model"
type="text"
placeholder="Full Name">
></b-form-input>
<div class="error" v-if="!$v.name.required">Field is required</div>
<div class="error" v-if="!$v.name.minLength">Name must have at least {{$v.name.$params.minLength.min}} letters.</div>
</b-form-group>
<b-form-group :class="{'form-group--error': $v.phone.$error}">
<b-form-input
id="phone"
v-model.trim="$v.phone.$model"
type="number"
placeholder="Phone Number">
></b-form-input>
<div class="error" v-if="!$v.phone.required">Field is required</div>
</b-form-group>
<b-form-group :class="{'form-group--error': $v.email.$error}">
<b-form-input
id="email"
v-model.trim="$v.email.$model"
type="email"
placeholder="Email Address">
></b-form-input>
<div class="error" v-if="!$v.email.required">Field is required</div>
</b-form-group>
<div class="d-flex align-items-end">
<b-form-group :class="{'form-group--error': $v.message.$error}">
<b-form-textarea
id="message"
v-model.trim="$v.message.$model"
type="text"
placeholder="Message"
></b-form-textarea>
<div class="error" v-if="!$v.message.required">Field is required</div>
<div class="error" v-if="!$v.message.minLength">Name must have at least {{$v.message.$params.minLength.min}} characters.</div>
</b-form-group>
<b-form-group>
<b-button
type="submit"
variant="secondary"
v-html="'S'"
:disabled="submitting" />
</b-form-group>
</div>
</form>
script:
export default {
mixins: [validationMixin],
components: {
subscribeBox
},
data() {
return {
map: bgMap,
name: "",
phone: "",
email: "",
message: "",
submitting: false,
isSubmitted: false,
error: false,
}
},
validations: {
name: {
required,
minLength: minLength(4)
},
phone: {
required,
},
email: {
required,
email
},
message: {
required,
minLength: minLength(5)
}
},
methods: {
validate() {
if (this.$v.$invalid || this.$v.$error|| this.submitting) {
this.$v.$touch();
return
}
this.onSsubmit();
},
async onSsubmit() {
this.submitting = true;
this.error = false;
try {
await this.$axios.$post('/api/v1/send-email', {
name: this.name,
phone: this.phone,
email: this.email,
message: this.message,
});
this.submitting = false
this.isSubmitted = true
await new Promise(resolve => setTimeout(resolve, 2500))
} catch(e) {
this.submitting = false
this.error = true
console.error(e)
}
},
},
}
nuxt.config.js
serverMiddleware: ['~/api/v1/send-email.js'],
api/v1/send-email.js (all the API Keys are placed in .env file)
const express = require("express");
const bodyParser = require('body-parser')
const sgMail = require('#sendgrid/mail');
const app = express();
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
app.use(bodyParser.json())
app.post("/", (req, res) => {
let msg = {
to: req.body.email, // Change to your recipient
from: '', // Change to your verified sender
subject: 'Message from ' + req.body.name,
text: 'telephone ' + req.body.phone + ', ' + 'message ' + req.body.message,
}
sgMail
.send(msg)
.then(() => {
return res.status(200).json({ 'message': 'Email sent!' })
})
.catch((error) => {
return res.status(400).json({ 'error': 'Opsss... Something went wrong ' + error })
})
});
module.exports = {
path: "/api/v1/send-email",
handler: app
};
This app is still not finished but the code is working 100%!
I'm new to Nuxt.js so some bits may not look awesome but I'm also happy and open to feedback and suggestions.
Thank you for reading my post and good luck with your project! :)

problems with Vuex data rendering

I'm studying reactivity of vuex using nuxt and module mode of store. The problem is, that despite all data in store is changed by actions => mutations successfully, they do not appear on the page, and shows only empty new element of store array. here are my files:
store>contacts>index.js:
let initialData = [
{
id: 1,
name: 'Michael',
email: 'michael.s#mail.com',
message: 'message from Michael'
},
{
id: 2,
name: 'Mark',
email: 'mark.sh#email.com',
message: 'message from Mark'
},
{
id: 3,
name: 'Valery',
email: 'valery.sh#mail.com',
message: 'message from Valery'
}
]
const state = () =>{
return {
contacts: []
}
}
const getters = {
allContacts (state) {
return state.contacts
}
}
const actions = {
async initializeData({ commit }) {
commit('setData', initialData)
},
addNewContact({ commit, state }, newContact) {
commit('addContact', newContact)
}
}
const mutations = {
setData: (state, contacts) => (state.contacts = contacts),
addContact: (state, newContact) => state.contacts.push(newContact)
}
export default { state, getters, mutations, actions}
component itself:
<template>
<div class="contact-form">
<div class="links">
<nuxt-link to="/">home</nuxt-link>
<nuxt-link to="/contact-form">contact form</nuxt-link>
</div>
<h1>leave your contacts and message here:</h1>
<div class="input-wrapper">
<form class="feedback-form" action="">
<div class="name">
<label for="recipient-name" class="col-form-label">Ваше имя:</label>
<input type="text" id="recipient-name" v-model="obj.userName" name="name" class="form-control" placeholder="Представьтесь, пожалуйста">
</div>
<div class="form-group">
<label for="recipient-mail" class="col-form-label">Ваш email:</label>
<input type="email" v-model="obj.userEmail" name="email" id="recipient-mail" class="form-control" placeholder="example#mail.ru">
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Сообщение:</label>
<textarea name="message" v-model="obj.userMessage" id="message-text" class="form-control"></textarea>
</div>
<button #click.prevent="addToStore()" type="submit">submit</button>
</form>
</div>
<h3>list of contacts</h3>
<div class="contacts-list">
<div class="list-element" v-for="contact in allContacts" :key="contact.id">
id: {{contact.id}} <br> name: {{contact.name}}<br/> email: {{contact.email}}<br/> message: {{contact.message}}
</div>
</div>
</div>
</template>
<script>
import { mapMutations, mapGetters, mapActions } from 'vuex'
export default {
data() {
return {
obj: {
userName: '',
userEmail: '',
userMessage: ''
}
}
},
mounted() {
console.log(this.showGetters)
},
created() {
this.initializeData()
},
methods: {
...mapActions({
initializeData: 'contacts/initializeData',
addNewContact: 'contacts/addNewContact'
}),
addToStore() {
this.addNewContact(this.obj)
},
},
computed: {
...mapGetters({
allContacts: 'contacts/allContacts',
}),
showGetters () {
return this.allContacts
}
},
}
</script>
so, could anybody help to understand, what is wrong?
You've got mismatched field names.
Inside obj you've called them userName, userEmail and userMessage. For all the other contacts you've called them name, email and message.
You can use different names if you want but somewhere you're going to have to map one onto the other so that they're all the same within the array.
You should be able to confirm this via the Vue Devtools. The first 3 contacts will have different fields from the newly added contact.

Simple If return in VUE.JS method

Template won't show v-if when method return true.
I can log the username password and the failed attempt in the else section of my login method. If I try to log the loginFailed var I get loginFailed is not defined.
<template>
<div class="container-big">
<div class="container-header">
<h1>Our Login</h1>
</div>
<p v-if="loginFailed">
Login Failed
</p>
<div id="loginContainer" class="container-login">
<P>{{msg}}</p>
<input id="login" type="text" placeholder="username" v-model="username" spellcheck="false" >
<input id="key" type="password" placeholder="password" v-model="password" spellcheck="false" >
</div>
<div class="container-signin">
<button class="signin" id="go" #click="login()"></button>
</div>
</div>
</template>
<script>
export default {
name: 'Login',
data () {
return {
msg: 'This is your login',
password: '',
username: '',
loginFailed: false
}
},
methods: {
login () {
console.log(this.username);
console.log(this.password);
if (this.username !== '' && this.password === 'pass') {
this.$router.push( { name: 'dashboard', path: '/dashboard' }) }
else {
console.log('failed attempt')
this.loginFailed === true;
return this.loginFailed
}
}
}
}
</script>
What I want to do is if login failed show
<p v-if="loginFailed">
Login Failed
</p>
In the login() method, you're performing a comparison when you should actually assign a value to the loginFailed property:
this.loginFailed === true;
Change that line so that it actually assigns true to this.loginFailed:
this.loginFailed = true;
side note: You most likely don't need to return anything from login() since it's simply dealing with instance properties, you might want to remove this line:
return this.loginFailed

Vee-Validate custom date validation

I was wondering if there is anyway you can write a custom date validation using vee-validate plugin where the end date cannot be less than the start date? I have looked high and low, and there is nowhere I can find a definite answer to this.
If there is no way to implement this, then I can make do without it, however, right now what I have implemented in my template for my start date is:
<input type="text" id="startDate" name="startDate" class="form-control" v-model="startDate" v-validate="'required|date_format:DD-MM-YYYY'" :class="{'input': true, 'is-danger': errors.has('startDate') }">
<label class="mb-0" for="startDate">Start Date</label>
<span v-show="errors.has('startdate')" class="text-danger"><center>{{ errors.first('startdate') }}</center></span>
My script looks like this:
export default {
name: 'App',
data: () => ({
task: '',
startDate: '',
startTime: '',
endDate: '',
endTime: '',
description: 'test'
}),
methods: {
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
// eslint-disable-next-line
alert('Form Submitted!');
return;
}
alert('Correct them errors!');
});
}
}
};
But there is no validation that is showing up. I think I am missing something in my script but I am not sure how to implement the date into there. Any help would be greatly appreciated.
First, it's maybe some typo error but in your template you use startDate and startdate lowercased.
Then, to answer your question, it's possible to define a custom validator with a date comparison with vee-validate.
As your chosen date format "DD-MM-YYYY" is not a valid javascript Date format, the string dates need to be rewritten into a valid format to make it work.
Vue.use(VeeValidate)
new Vue({
el: "#app",
data() {
return {
startDate: '',
endDate: '',
}
},
created() {
let self = this
this.$validator.extend('earlier', {
getMessage(field, val) {
return 'must be earlier than startDate'
},
validate(value, field) {
let startParts = self.startDate.split('-')
let endParts = value.split('-')
let start = new Date(startParts[2], startParts[1] -1, startParts[0]) // month is 0-based
let end = new Date(endParts[2], endParts[1] -1, endParts[0])
return end > start
}
})
},
methods: {
validateBeforeSubmit() {
this.$validator.validateAll().then((result) => {
if (result) {
alert('Form Submitted!');
return;
}
alert('Correct them errors!');
});
}
}
})
.is-danger, .text-danger {
color: red;
}
<script src="https://unpkg.com/vee-validate#2.0.0-rc.19/dist/vee-validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div>
<input type="text" name="startDate" v-model="startDate"v-validate="'required|date_format:DD-MM-YYYY'" :class="{'input': true, 'is-danger': errors.has('startDate') }">
<label class="mb-0" for="startDate">Start Date</label>
<span v-show="errors.has('startDate')" class="text-danger">{{ errors.first('startDate') }}</span>
</div>
<div>
<input type="text" name="endDate" v-model="endDate" v-validate="'required|date_format:DD-MM-YYYY|earlier'" :class="{'input': true, 'is-danger': errors.has('endDate') }">
<label class="mb-0" for="endDate">End Date</label>
<span v-show="errors.has('endDate')" class="text-danger">{{ errors.first('endDate') }}</span>
</div>
<button #click="validateBeforeSubmit">Save</button>
</div>
Note: i put the custom validator inside the created hook for the example but you can put it inside any file you want in your projet. Just import it correctly as the documentation recommends.