How to use signature along to reactive hook? - javascript

I would like to show the backend error messages on my Vue component, so for doing that I have created this component:
<template>
<section class="p-0 d-flex align-items-center">
<div class="container-fluid">
<div class="row">
<!-- Right START -->
<div class="col-md-12 col-lg-8 col-xl-9 mx-auto my-5 position-relative">
<!-- Shape Decoration END -->
<div class="row h-100">
<div
class="
col-md-12 col-lg-10 col-xl-5
text-start
mx-auto
d-flex
align-items-center
"
>
<div class="w-100">
<h3>Sign up for your account!</h3>
<p>
Join us today! Create your account easily with less
information.
</p>
<!-- Form START -->
<form class="mt-4" #submit.prevent="submit">
<!-- Email -->
<div class="mb-3">
<label class="form-label" for="email">Email address</label>
<input
v-model="data.name"
v-bind:class="{ 'is-invalid': validate.email }"
required
type="email"
class="form-control"
id="email"
aria-describedby="emailHelp"
placeholder="E-mail"
/>
<div class="invalid-feedback">
{{ validate.email }}
</div>
<small id="emailHelp" class="form-text text-muted"
>We'll never share your email with anyone else.</small
>
</div>
<!-- Username -->
<div class="mb-3">
<label class="form-label" for="email">Username</label>
<input
v-model="data.username"
v-bind:class="{ 'is-invalid': validate.username }"
required
type="text"
class="form-control"
id="username"
placeholder="Username"
/>
<div class="invalid-feedback">
{{ validate.username }}
</div>
</div>
<!-- Password -->
<div class="mb-3">
<label class="form-label" for="password">Password</label>
<input
v-model="data.password"
v-bind:class="{ 'is-invalid': validate.password }"
required
type="password"
class="form-control"
id="password"
placeholder="*********"
/>
<div class="invalid-feedback">
{{ validate.password }}
</div>
</div>
<!-- Password -->
<div class="mb-3">
<label class="form-label" for="password2"
>Confirm Password</label
>
<input
v-model="data.password2"
v-bind:class="{ 'is-invalid': validate.password2 }"
type="password"
class="form-control"
id="password2"
placeholder="*********"
/>
<div class="invalid-feedback">
{{ validate.password2 }}
</div>
</div>
<!-- Checkbox -->
<div class="mb-3 form-check">
<input
type="checkbox"
class="form-check-input"
id="remember"
/>
<label class="form-check-label" for="remember"
>keep me signed in</label
>
</div>
<!-- Button -->
<div class="row align-items-center">
<div class="col-sm-4">
<button type="submit" class="btn btn-dark btn-line">
Sign me up
</button>
</div>
<div class="col-sm-8 text-sm-end">
<span class="text-muted"
>Already have an account?
Signin here</span
>
</div>
</div>
</form>
<!-- Form END -->
<div class="bg-dark-overlay-dotted py-2 my-4"></div>
</div>
</div>
</div>
</div>
<!-- Right END -->
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
import { useRouter } from "vue-router";
export default defineComponent({
name: "Register",
setup(props) {
const data = reactive({
username: "",
email: "",
password: "",
password2: "",
});
const validate = reactive({
email: "",
username: "",
password: "",
password2: "",
});
const router = useRouter();
const submit = async () => {
const res = await fetch(`${process.env.VUE_APP_API_URL}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok) {
const errors = await res.json().then((response) => response.errors);
for (const err of errors) {
const param: string = err.param ?? "";
validate[param] = err.msg;
}
}
};
return {
data,
validate,
submit,
};
},
});
</script>
as you can see I have defined and also exposed the validate property, which contains all the fields of the form.
When the API call is executed on the form submit, I reiceve this response if the backend validation fails:
{
"errors": [
{
"value": "",
"msg": "username must be at least 4 characters long",
"param": "username",
"location": "body"
},
{
"value": "test#",
"msg": "password confirm is different",
"param": "password2",
"location": "body"
}
]
}
I binded the validate property to each input field, so if the value entered in a specific field is incorrect, an error will be appended near the field and also the is-invalid class of Bootstrap is applied.
The errors variable contains the response above, what I'm trying to do is assign to each property of validate (which are the same name of the fields), the error messages, and I did:
const param: string = err.param ?? "";
validate[param] = err.msg;
the problem's that I get:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ email: string; username: string; password: string; password2: string; }'.
No index signature with a parameter of type 'string' was found on type '{ email: string; username: string; password: string; password2: string; }'.
How can I fix this situation? And also, is there a better way to handle such scenario? 'cause I'm new to Vue and most probably I'm overcomplicating the situation here.
Kind regards

If you decided to add TypeScript to your project, it would be a good idea to start using it as intended, so let's firstly make a type for your error object:
type MyTypedError = {
value: string,
msg: string,
// the question mark indicates that the "param" property may not exists
// "typeof" infers the type of the "validate" variable (object in your case)
// "keyof" grabs all the property names from "validate":
param?: keyof typeof validate,
location: string
}
Let's make use of it. Now the TypeScript compiler will know the type of the received data.
if (!res.ok) {
// specify the type of the "errors" variable:
// MyTypedError[] (array of MyTypedError objects)
const errors: MyTypedError[] = await res.json().then((response) => response.errors);
...
}
The problem with your code: you are only guaranteeing that the param variable is type of string, but you don't promise the compiler that param holds any of the property names from validate. But since the compiler knows the type of err, it will even guide you how to make a working loop:
for (const err of errors) {
// We stated that 'param' property might not exist
if (err.param !== undefined) validate[err.param] = err.msg
}
EDIT: The assumption that err.param can also be undefined came from your code: err.param ?? "", though I don't see any reason why would it be.

Related

How to clear the form input fileds when user successfully submitted the form in Vue.js?

i have one registration page when user renter some values ,after successfully submission the form need to be clear all the field for that i am using predefined function called reset() inside the script section ,it's not working i am unable to get where did i mistake and one more thing autocomplete="off" is also not working please help me to fix this issue.
Register.vue
<template>
<div class="main">
<div class="container">
<img src="../assets/sideImg.png" alt="notFound" />
<p>Online Book Shopping</p>
<div class="box">
<div class="headings">
<h5 class="signin">Login</h5>
<h5 class="signup">signup</h5>
</div>
<form id="myForm" #submit.prevent="handlesubmit">
<div class="fullname">
<p>FullName</p>
<input type="text" class="namebox" required v-model="FullName" autocomplete="off" pattern="[A-Za-z]{3,12}">
</div>
<div class="username">
<p>EmailID</p>
<input type="email" class="emailbox" required v-model="EmailID" pattern="^[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,4}$">
</div>
<div class="pass">
<p>Password</p>
<input :type="password_type" class="password" v-model="Password" pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$!%*?&])[A-Za-z\d#$!%*?&]{6,}$" required>
<i class="bi bi-eye-slash" id="togglePassword"></i>
</div>
<div class="mobile">
<p>MobileNumber</p>
<input type="tel" class="telephone" v-model="Mobile" pattern="^\d{10}$" required>
</div>
<button class="btn-section" #click="clear();" type="submit">Signup</button>
</form>
</div>
</div>
</div>
</template>
<script>
import service from '../service/User'
export default {
name: 'Register',
data() {
return {
FullName: '',
EmailID: '',
Password: '',
Mobile: '',
password_type: "password",
}
},
methods: {
togglePassword() {
this.password_type = this.password_type === 'password' ? 'text' : 'password'
},
clear(){
document.getElementById("myForm").reset();
},
handlesubmit() {
let userData = {
FullName: this.FullName,
EmailID: this.EmailID,
Password: this.Password,
Mobile: this.Mobile
}
service.userRegister(userData).then(response => {
// console.log("user Details", response);
alert("user registered successfully");
return response;
}).catch(error => {
alert("Invalid Email/Mobile number");
return error;
})
}
}
}
</script>
<style lang="scss" scoped>
#import "#/styles/Register.scss";
</style>
try this:
in your HTML
<form ref="myForm" #submit.prevent="handlesubmit">
in your handlesubmit, after it finishes
this.$refs.myForm.reset();
about autocomplete, found this:
In order to provide autocompletion, user-agents might require elements to:
Have a name and/or id attribute
Be descendants of a element
The form to have a submit button
try adding a name or id to the input.

Vue: login validation not working in my code

I'm creating login validation in Vue.js but the error message is not displaying and it gives me the error:
Property or method "error" 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.
Any help?
Template:
<template>
<div class="container" width="900">
<div class="row justify-content-center" style="margin-top: 10px;">
<div class="col-5">
<div v-if="error" class="alert alert-danger" role="alert">
{{error}}
</div>
<div class="card" >
<div class="card-text">
<div class="form-group" #keyup.enter="logItIn">
<input class="form-control"
v-model="login.email"
label="Email"
placeholder="Email Address"
required
> <br>
<input class="form-control"
v-model="login.password"
label="Password"
type="password"
placeholder="Password"
required>
</div>
</div>
<button type="button" class="btn btn-secondary" #click='logItIn'>Login</button>
</div>
</div>
</div>
</div>
</template>
Script:
import axios from 'axios'
export default {
data() {
return {
login: {
email:"",
password:"",
error: ""
}
}
},
methods: {
async logItIn() {
try {
axios.post('https://odevin-api.herokuapp.com/login',this.login)
.then(response => {
console.log(response)
let newToken=response.data.response.token;
window.token=newToken;
let user=response.data.response.user; //response
localStorage.setItem('token',newToken);
localStorage.setItem('user',JSON.stringify(user));
window.axios.defaults.params={api_token:newToken}
// Event.$emit('login',user);
this.$router.push('/');
});
} catch(e) {
this.error = 'invalid user name or password';
}
}
}
}
you referenced {{ error }} in your template but in your data object, error is a property of login object. so vue can't find it properly.
either change the usage in your template to {{ login.error }} or define it in your data object like this:
data() {
return {
error: '',
login: {
email: '',
password: '',
},
}
}

TypeError: res.response is undefined

I'm doing an user authentication using JWT auth in a SPA with Vue/Laravel. I have an issue with the register module, it isn't doing anything when I click the button, I checked Firefox developer edition's console and it throws me the following error:
TypeError: res.response is undefined
This is my code
<template>
<div class="container">
<div class="card card-default">
<div class="card-header">Inscription</div>
<div class="card-body">
<div class="alert alert-danger" v-if="has_error && !success">
<p v-if="error == 'registration_validation_error'">Error</p>
<p v-else>Try again later.</p>
</div>
<form autocomplete="off" #submit.prevent="register" v-if="!success" method="post">
<div class="form-group" v-bind:class="{ 'has-error': has_error && errors.email }">
<label for="email">E-mail</label>
<input type="email" id="email" class="form-control" placeholder="user#example.com" v-model="email">
<span class="help-block" v-if="has_error && errors.email">{{ errors.email }}</span>
</div>
<div class="form-group" v-bind:class="{ 'has-error': has_error && errors.password }">
<label for="password">Mot de passe</label>
<input type="password" id="password" class="form-control" v-model="password">
<span class="help-block" v-if="has_error && errors.password">{{ errors.password }}</span>
</div>
<div class="form-group" v-bind:class="{ 'has-error': has_error && errors.password }">
<label for="password_confirmation">Confirm password</label>
<input type="password" id="password_confirmation" class="form-control" v-model="password_confirmation">
</div> <button type="submit" class="btn btn-default" #click="register">Inscription</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
name: '',
email: '',
password: '',
password_confirmation: '',
has_error: false,
error: '',
errors: {},
success: false
}
},
methods: {
register() {
var app = this
this.$auth.register({
data: {
email: app.email,
password: app.password,
password_confirmation: app.password_confirmation
},
success: function () {
app.success = true
this.$router.push({
name: 'Login',
params: {
successRegistrationRedirect: true
}
})
},
error: function (res) {
console.log(res.response.data.errors)
app.has_error = true
app.error = res.response.data.error
app.errors = res.response.data.errors || {}
}
})
}
}
}
</script>
If this gives some sort of hint I also have this warning in console:
Reason: CORS request did not succeed
I have no idea why is this happening.
You're running into a CORS error which means that the server you're making a request to is not allowing requests from your client domain. If you own the server you'll need to add your domain to the list of allowed domains, or if this fails, check that the request method "POST" is allowed.
It looks like you're then getting the 'res.response" error as when the error gets caught in the error function, it's returning an error message which doesn't have a "response" property. Try console.log'ing the "res" response to see what it's giving you and adjust the property you're using from there.

How can I use field validation in my Vue wizard form?

In an example of a Vue wizard form I tried to add form validation with Joi. How do I set this up logically? The goal is to controll the fields before moving to the second and last page with the next() method. Because of the simplicity of this wizard form, I don't want to change to VueFormWizard. To increase the code I erased a lot of fields etc..
<template>
<div>
<div v-if="errorMessage" class="alert alert-danger" role="alert">
{{errorMessage}}
</div>
<form>
<div v-if="step ===1 ">
<div class="form-group">
<label for="title">Title</label>
<input v-model="example.title"
type="text"
class="form-control"
id="title" />
</div>
<button #click.prevent="next()">Next step</button>
</div>
<div v-if="step === 2">
<div class="form-group">
<label for="userName">Email.</label>
<input v-model="example.userName"
type="email"
class="form-control"
id="userName" />
</div>
<button #click.prevent="prev()">Go back</button>
<button #click.prevent="createExample" type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</template>
<script>
import Joi from 'joi'
const schema = Joi.object().keys({
title: Joi.string().alphanum().min(2).max(40).required(),
userName: Joi.string().email(),
})
export default {
data: () => ({
step: 1,
errorMessage: false,
example: {
title: '',
userName: ''
}
}),
watch: {
example: {
handler () {
this.errorMessage = ''
},
deep: true
}
},
methods: {
prev () {
this.step--
},
next () {
this.step++
if (this.validUser()) {
return false
}
},
createExample () {
// Post request
},
validUser () {
const result = Joi.validate(this.huismap, schema)
return true
if (result.error.message.includes('title')) {
this.errorMessage = 'Vul een titel in van min 2 karakters'
return false
}
}
}
</script>
You can make use of browser validation if you set it up like this:
<form #submit.prevent="submitMyForm">
<input v-model="form.title" required minlength="4" maxlength="20" />
<button type="submit">Submit</button>
</form>
Now your browser will prevent you from submitting the form if title is empty, if the length is less than 4 or greater than 20.
This solution can do a lot of stuff, even regex checking:
https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation#Validating_against_a_regular_expression
However this is limited to a small set of checks and is not supported by older browsers. If you need very specific validation you'd have to use a custom solution, which is described here https://v2.vuejs.org/v2/cookbook/form-validation.html.

Angular 2 custom validator how to set control.valid true?

I have such custom validator:
static isEmail(control:Control):{[key:string]:boolean} {
let emailRegExp = new RegExp("^[-a-z0-9~!$%^&*_=+}{'?]+(.[-a-z0-9~!$%^&*_=+}{'?]+)*#([a-z0-9_][-a-z0-9_]*(.[-a-z0-9_]+)*.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}))(:[0-9]{1,5})?$", 'i');
if (control.value.match(emailRegExp)) {
return {isEmail: true};
}
return {isEmail: false};
}
Use it in component:
loginForm:ControlGroup;
constructor(private _router:Router,
private _loginService:LoginService,
private _formBuilder:FormBuilder) {
this.loginForm = _formBuilder.group({
email: ['', Validators.compose([
Validators.required,
Validators.minLength(8),
FormValidationService.isEmail
])],
password: ['', Validators.compose([
Validators.required,
Validators.minLength(8),
Validators.maxLength(30),
FormValidationService.isPassword
])]
});
}
And template:
<form id="login-form" role="form" [ngFormModel]="loginForm" (ngSubmit)="onLoginSubmit()">
<div class="form-group">
<label class="control-label" for="loginFormEmail">Email</label>
<input type="text" id="loginFormEmail" class="form-control"
ngControl="email" #email="ngForm">
<div *ngIf="email.dirty && !email.valid">
<div class="alert alert-danger" role="alert" [hidden]="!email.errors.minlength">
Email field must have more then 8 characters
</div>
<div class="alert alert-danger" role="alert" [hidden]="email.errors.isEmail">
Email not valid
</div>
</div>
</div>
<div class="form-group">
<label class="control-label" for="loginFormPassword">Password</label>
<input type="password" id="loginFormPassword" class="form-control"
ngControl="password" #password="ngForm">
<div *ngIf="password.dirty && !password.valid">
<div class="alert alert-danger" role="alert" [hidden]="!password.errors.minlength">
Password field must have more then 8 characters
</div>
<div class="alert alert-danger" role="alert" [hidden]="!password.errors.maxlength">
Password field must have no more then 30 characters
</div>
<div class="alert alert-danger" role="alert" [hidden]="password.errors.isPassword">
Password must meet the following rules:
<ul>
<li>At least one upper case english letter</li>
<li>At least one lower case english letter</li>
<li>At least one digit</li>
<li>At least one special character</li>
</ul>
</div>
</div>
</div>
<p>If you forgot your password you can <a (click)="toForgot()">reset it</a>.</p>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block" [disabled]="!loginForm.valid">Login</button>
</div>
</form>
If you look in screenshot you can see that custom validator return my value. But regardless of its value control.valid always false, so form invalid to.
I try to set control.valid = true, but this field have only getter.
I found answer. When control value is valid you must return null in your custom validator:
static isPassword(control:Control):{[key:string]:boolean} {
let passwordRegExp = new RegExp("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{8,}$");
if (control.value.match(passwordRegExp)) {
return null;
}
return {isPassword: false};
}
I think you need to add ngControlGroup to wrapping elements otherwise the form won't track the containing input elements
<div class="form-group" ngControlGroup="someName">
See also NgControlGroup
You mixed inline form definition with ngForm and definition in JS code using the FormBuilder class.
You should refactor your input definitions this way to use the ngFormControl directive:
<input [ngFomControl]="loginForm.controls.password" .../>

Categories

Resources