I want to validate using vue vee-validate and baseinputs. But I cannot use v-model where I call the component.
my TextInput Component.
<template>
<div
class="TextInput"
:class="{ 'has-error': !!errorMessage, success: meta.valid }"
>
<label :for="name">{{ label }}</label>
<input
:name="name"
:id="name"
:type="type"
:value="inputValue"
:placeholder="placeholder"
#input="handleChange"
#blur="handleBlur"
/>
<p class="help-message" v-show="errorMessage || meta.valid">
{{ errorMessage || successMessage }}
</p>
</div>
</template>
<script>
import { useField } from "vee-validate";
export default {
props: {
type: {
type: String,
default: "text",
},
value: {
type: String,
default: "",
},
name: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
successMessage: {
type: String,
default: "",
},
placeholder: {
type: String,
default: "",
},
},
setup(props) {
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
meta,
} = useField(props.name, undefined, {
initialValue: props.value,
});
return {
handleChange,
handleBlur,
errorMessage,
inputValue,
meta,
};
},
};
App.vue file where I call the component
<template>
<div>
{{ test }}
<Form
#submit="onSubmit"
:validation-schema="schema"
#invalid-submit="onInvalidSubmit"
>
<TextInput
name="name"
type="text"
label="Full Name"
placeholder="Your Name"
success-message="Nice to meet you!"
v-model="test"
/>
<button class="submit-btn" type="submit">Submit</button>
</Form>
</div>
</template>
<script>
import { ref } from "vue";
import { Form } from "vee-validate";
import * as Yup from "yup";
import TextInput from "./components/TextInput.vue";
export default {
name: "App",
components: {
TextInput,
Form,
},
setup() {
const test = ref("asda");
function onSubmit(values) {
alert(JSON.stringify(values, null, 2));
}
function onInvalidSubmit() {
const submitBtn = document.querySelector(".submit-btn");
submitBtn.classList.add("invalid");
setTimeout(() => {
submitBtn.classList.remove("invalid");
}, 1000);
}
const schema = Yup.object().shape({
name: Yup.string().required(),
email: Yup.string().email().required(),
password: Yup.string().min(6).required(),
confirm_password: Yup.string()
.required()
.oneOf([Yup.ref("password")], "Passwords do not match"),
});
return {
onSubmit,
schema,
onInvalidSubmit,
test,
};
},
};
</script>
where I wrote in v-model ="test" doesn't work. I tried to send it with emit but I couldn't.
So, in summary, let the component I validate perform the validation process, but I can use the v-model where I call the component.
Related
The plan is I give to this factory class an array of objects, and that gives me beck a Formik config with filled Yup validation. After then the getFrom method of my factory class generates the entire form also that can I directly rendering now with react.
But I doen't know any method to solve that for example Yup.string().required().max(2, "error") method chain built up in my class.
Plases give me some direction where can I find solutions for this problem.
// this the form describing array of objects:
const exampleFields = [
{
name: "First name",
inputType: "text",
valueType: "string",
label: "Enter your first name",
initialValue: "",
placeholder: "Enter first name here...",
stylingClasses: {
inputCotanierClasses: "w-100",
labelClasses: "text-left",
inputClassess: "border-0 border-bottom border-dark",
errorClasses: "text-danger",
},
validation: {
required: { message: "First name is required" },
min: {
value: 2,
message: "First name must be at least 2 characters",
},
max: {
value: 15,
message: "first name must be at most 15 characters",
},
},
},
{
name: "Last name",
inputType: "number",
valueType: "number",
label: "Enter your last name",
initialValue: "",
placeholder: "Enter last name here...",
validation: {
required: { message: "Last name is required" },
min: {
value: 2,
message: "Last name must be at least 2 characters",
},
max: {
value: 10,
message: "Last name must be at most 10 characters",
},
},
},
{
name: "email",
inputType: "text",
valueType: "email",
label: "Enter your email",
initialValue: "",
placeholder: "Enter yout emial here...",
validation: {
required: { message: "Email is required" },
email: true,
},
},
];
//and this is my factory class:
import React from "react";
import * as Yup from "yup";
export default class FormFactory {
constructor(fields, submit, Yup) {
this.submit = submit;
const buildUpValidation = (Yup, validation, { message, value }) => {
return Yup[validation](message, value);
};
fields.forEach((field) => {
const initialYup = Yup[field.valueType]();
const validations = Object.entries(field.validation);
validations.forEach((validation) => {
const [validationType, params] = validation;
console.log(validationType, params);
buildUpValidation(initialYup, validationType, params || true);
});
});
//this.validationSchema = Yup.object().shape({});
//console.log(this.validationSchema);
}
getConfig() {
return {
initialValues: this.initialValues,
validationSchema: this.validationSchema,
onSubmit: (values) => this.submit(values),
};
}
generateField(
formik,
{ label, id, name, key, inputType, placeholder, stylingClasses }
) {
return (
<div
key={key}
className={`input-container ${stylingClasses?.inputCotanierClasses && stylingClasses.inputCotanierClasses}`
}
>
{label && (
<label
className={
stylingClasses?.labelClasses && stylingClasses.labelClasses
}
htmlFor={id || name}
>
{label}
</label>
)}
<input
id={id || name}
className={
stylingClasses?.inputClassess && stylingClasses.inputClassess
}
name={name || id}
type={inputType || "text"}
value={formik.values[name || id]}
placeholder={placeholder}
onBlur={formik.handleBlur}
onChange={formik.handleChange}
/>
<>
{formik.touched[name || id] && formik.errors[name || id] ? (
<p
className={
stylingClasses.errorClasses && stylingClasses.errorClasses
}
>
{formik.errors[name || id]}
</p>
) : null}
</>
</div>
);
}
getForm(formik) {
return this.fields.map((field, key) =>
this.generateField(formik, { ...field, key })
);
}
}
//and the usage is the followings:
import React from "react";
import { useFormik } from "formik";
import FormFactory from "./FormFactory";
const SignUpFormikFactory = () => {
const singUpForm = new FormFactory(exampleFields, (values) =>
console.log(values)
);
// console.log(singUpForm.getConfig());
const formik = useFormik({ ...singUpForm.getConfig() });
// console.table("| formik.values", formik.values);
// console.table("| formik.errors", formik.errors);
// console.table("| formik.touched", formik.touched);
return (
<form onSubmit={formik.handleSubmit}>
<div className="form-container">
{singUpForm.getForm(formik)}
<button type="submit">Submit</button>
</div>
</form>
);
};
export default SignUpFormikFactory;
/*********************/
/* And in the app is */
/*********************/
import "./App.css";
import SignUp from "./SignUp";
import SignUpFormik from "./SignUpFormik";
import SignUpFormikFactory from "./SignUpFormikFactory";
function App() {
return (
<div className="App">
<SignUpFormikFactory />
</div>
);
}
export default App;
I have this simple registration page:
<template>
<div class="login">
<div class="login-content">
<h1 #click="redirect('/')">Logo</h1>
</div>
<div class="login-header">
<p class="paragraph-small right">Already have account?
<span class="paragraph-small pointer link" #click="redirect('/login')">Log in!</span>
</p>
</div>
<div class="login-inputs">
<div class="login-inputs-container">
<h1>Sign up</h1>
<Input :error="error" :title="'Email'" :type="'email'" :value="email" />
<Input :error="error" :title="'Password'" :type="'password'" :value="password" />
<Input :error="error" :title="'Repeat password'" :type="'password'" :styles="'padding-bottom: 10px'" :value="passwordRepeat" />
<Checkbox :value="tac" :label="`I have read and accepted <a href='/'>terms and conditions.</a>`" />
<Button :label="'Sign up'" :clickon="register" />
<p v-if="error">Passwords have to match!</p>
</div>
</div>
</div>
</template>
<script>
import { register } from "~/api";
import { mapGetters, mapState, mapActions, mapMutations } from 'vuex';
import Input from "~/components/Input";
import Button from "~/components/Button";
import Checkbox from "~/components/Checkbox";
export default {
name: "register",
components: {
Input,
Button,
Checkbox
},
watch: {
password() { this.error = (this.password !== this.passwordRepeat) && (this.password !== null && this.passwordRepeat !== null) },
passwordRepeat() { this.error = (this.password !== this.passwordRepeat) && (this.password !== null && this.passwordRepeat !== null) }
},
computed: {
...mapGetters({
email: 'register/getEmail',
password: 'register/getPassword',
passwordRepeat: 'register/getPasswordRepeat',
status: 'register/getStatus',
error: 'register/getError',
tac: 'register/getTac'
})
},
methods: {
redirect(path) {
this.$router.push({ path })
},
async register() {
console.log(this.tac, this.password, this.passwordRepeat, this.email)
}
}
}
</script>
<style lang="scss">
#import "../assets/css/login";
</style>
As you can see, there are 4 fields where I want to change value - 3 Input and 1 Checkbox. When I provide data and click button in console I get the default values, I was trying to do something with mutations and actions, but it doesn't work.
Can it be because I use my components, not default?
Also, here is my store store/register.js
export const state = () => ({
email: null,
password: null,
passwordRepeat: null,
status: null,
error: false,
tac: false
})
export const mutations = {
setEmail(state, value) { state.email = value },
setPassword(state, value) { state.password = value },
setPasswordRepeat(state, value) { state.passwordRepeat = value },
setStatus(state, value) { state.status = value },
setError(state, value) { state.error = value },
setTac(state, value) { state.tac = value }
}
export const actions = {
fetchEmail(ctx, value) { ctx.commit('setEmail', value) },
fetchPassword(ctx, value) { ctx.commit('setPassword', value) },
fetchPasswordRepeat(ctx, value) { ctx.commit('setPasswordRepeat', value) },
fetchStatus(ctx, value) { ctx.commit('setStatus', value) },
fetchError(ctx, value) { ctx.commit('setError', value) },
fetchTac(ctx, value) { ctx.commit('setTac', value) },
}
export const getters = {
getEmail(state) { return state.email },
getPassword(state) { return state.password },
getPasswordRepeat(state) { return state.passwordRepeat },
getStatus(state) { return state.status },
getError(state) { return state.error },
getTac(state) { return state.tac },
}
If problem is that I use not default tags, but my components with props, here is Checkbox component:
<template>
<div class="checkbox-container">
<label class="container">
<input type="checkbox" :value="innerValue" #input="onInput">
<span class="checkmark"></span>
</label>
<p class="checkbox-paragraph" v-html="label" />
</div>
</template>
<script>
export default {
props: {
label: {
type: String,
default: ''
},
value: {
type: Boolean,
default: false
}
},
name: "Checkbox",
watch: {
value(value) {
this.innerValue = value
},
innerValue(value) {
this.$emit('input', value)
}
},
data() {
return {
innerValue: this.value
}
},
methods: {
onInput() {
this.$nextTick(() => {
this.innerValue = !this.innerValue
})
}
}
}
</script>
One way that can help you change the value of your checkbox is like this.
Checkbox Component:
<template>
<div class="checkbox-container">
<label class="container">
<input type="checkbox" #change="$emit('checkbox', value)" />
<span class="checkmark"></span>
</label>
</div>
</template>
<script>
export default {
name: 'Checkbox',
data() {
return {
value: false,
}
},
}
</script>
Now inside your register page you can use the checkbox component in template like this:
<Checkbox #checkbox="checkboxChanged" />
Now in the same page and in method section add this method:
checkboxChanged(event) {
this.$store.dispatch('register/fetchTac', event)
},
},
This way, when the value of checkbox changes you can have the changed value in your store too and get it with mapGetter. You can do the same to your inputs.
Okay, here is my working answer, I don't really know if it's correct, but it doesn't contain any errors or warnings:
<template>
<div class="login">
<div class="login-content">
<h1 #click="redirect('/')">Logo</h1>
</div>
<div class="login-header">
<p class="paragraph-small right">Already have account?
<span class="paragraph-small pointer link" #click="redirect('/login')">Log in!</span>
</p>
</div>
<div class="login-inputs">
<div class="login-inputs-container">
<h1>Sign up</h1>
<Input :error="error" :title="'Email'" :type="'email'" v-model="email" />
<Input :error="error" :title="'Password'" :type="'password'" v-model="password" />
<Input :error="error" :title="'Repeat password'" :type="'password'" :styles="'padding-bottom: 10px'" v-model="passwordRepeat" />
<Checkbox v-model="tac" :label="`I have read and accepted <a href='/'>terms and conditions.</a>`" />
<Button :label="'Sign up'" :clickon="register" />
<p v-if="error">Passwords have to match!</p>
</div>
</div>
</div>
</template>
<script>
import { register } from "~/api";
import { mapGetters, mapState, mapActions, mapMutations } from 'vuex';
import Input from "~/components/Input";
import Button from "~/components/Button";
import Checkbox from "~/components/Checkbox";
export default {
name: "register",
components: {
Input,
Button,
Checkbox
},
watch: {
...mapActions(['fetchTac', 'fetchError', 'fetchStatus', 'fetchPasswordRepeat', 'fetchPassword', 'fetchEmail']),
password() { this.error = (this.password !== this.passwordRepeat) && (this.password !== null && this.passwordRepeat !== null) },
passwordRepeat() { this.error = (this.password !== this.passwordRepeat) && (this.password !== null && this.passwordRepeat !== null) }
},
computed: mapGetters(['getError', 'getEmail', 'getPassword', 'getPasswordRepeat', 'getStatus', 'getTac']),
data() {
return {
email: null,
password: null,
passwordRepeat: null,
status: null,
error: false,
tac: false
}
},
methods: {
redirect(path) {
this.$router.push({ path })
},
async register() {
console.log(this.passwordRepeat, this.password, this.email, this.tac)
}
}
}
</script>
But I still have one problem, as you can see, I have getters and data at the same time, I can actually remove data, but it will cause such warning:
Property or method "propname" 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
It will work, but I will have this warning.
EDIT
I solved this problem that way:
computed: {
error: {
get() { return this.$store.getters.getError },
set(value) { this.$store.commit('setError', value) }
},
email: {
get() { return this.$store.getters.getEmail },
set(value) { this.$store.commit('setEmail', value) }
},
password: {
get() { return this.$store.getters.getPassword },
set(value) { this.$store.commit('setPassword', value) }
},
passwordRepeat: {
get() { return this.$store.getters.getPasswordRepeat },
set(value) { this.$store.commit('setPasswordRepeat', value) }
},
status: {
get() { return this.$store.getters.getStatus },
set(value) { this.$store.commit('setStatus', value) }
},
tac: {
get() { return this.$store.getters.getError },
set(value) { this.$store.commit('setTac', value) }
}
},
// data() {
// return {
// email: null,
// password: null,
// passwordRepeat: null,
// status: null,
// error: false,
// tac: false
// }
// },
I'm writing a unit test with vue 3 using vee-validate 4 and Jest. But I'm new to this and I'm stuck at one place.
I have a TextInput component that I use validations, and when I call this component, I do validation when submit is done.
There is a separate component where I write the form where I use these textInputs.
First let me show you the code in my TkTextInput component.
<template>
<div
class="tk-Input"
:class="{ 'has-error': !!errorMessage, success: meta.valid }"
>
<label class="tk-label" :for="name">{{ label }}</label>
<input
id="demos"
class="col-12"
v-model="inputValue"
:name="name"
:type="type"
:value="inputValue"
:placeholder="placeholder"
#input="handleChange"
#blur="handleBlur"
v-bind="$attrs"
/>
<p class="help-message" v-show="errorMessage || meta.valid">
{{ errorMessage }}
</p>
</div>
</template>
<script>
import {useField} from "vee-validate";
import {watch} from "vue";
export default {
props: {
type: {
type: String,
default: "text",
},
modelValue: String,
value: {
type: String,
default: "",
},
name: {
type: String,
required: true,
},
label: {
type: String,
},
placeholder: {
type: String,
default: "",
},
},
emits: ['update:modelValue'],
setup(props, {emit}) {
const {
value: inputValue,
errorMessage,
handleBlur,
handleChange,
meta,
} = useField(props.name, undefined, {
initialValue: props.value,
});
watch(inputValue, (val) => {
emit('update:modelValue', val);
});
watch(() => props.modelValue, (val) => {
if (val !== inputValue.value) {
inputValue.value = val;
}
})
return {
handleChange,
handleBlur,
errorMessage,
meta,
inputValue,
};
},
};
</script>
Then in my form component where I call these textInputs is as follows.
<Form
#submit="onSubmit"
:validation-schema="schema">
<div class="grid ">
<div class="col-12 lg:col-6 lg:mb-0">
<tk-text-input v-model.trim="vehicleInfo.Plate" label="Plaka*" name="Plate" type="text"/>
</div>
<div class="grid ">
<div class="col-12 lg:col-6 lg:mb-0">
<tk-text-input v-model.trim="vehicleInfo.PhoneNumber" label="Cep Telefonu*" name="PhoneNumber"/>
</div>
<div class="col-12 lg:col-6 lg:mb-0">
</div>
</div>
<Button #click="clicked" class=" p-button-success" type="submit">{{buttonLabel}}</Button>
</Form>
Now I want to test the validation process in the TkTextInput component when the button clicks when I click the button in the component where I wrote the form with gesture. But I couldn't do it.
The test I wrote in the .spec file is as follows.
describe('TkTextInput.vue', () => {
it('when validation is done', async() => {
const wrapperVehicle = mount(VehicleInfoDialog, {
global:{
plugins: [PrimeVue]
},
})
const button = wrapperVehicle.find("button")
button.trigger('submit')
await button.trigger("click")
expect(wrapperVehicle.html()).toContain("Boş Geçilemez.")
});
})
I have a problem with my generic input fields.
In other words I've made a generic input field which should cover regular input fields, checkboxes and radiobuttons. But when I try to pass a string value as a value of the radio input field, the prop is empty.
<TextInput
v-model="name"
description="Name & LastName"
name="Name & Surname"
rules="required"
/>
<TextInput
v-model="age"
type="number"
description="Age"
name="Age"
rules="required|digits:2"
/>
<div id="gender-fields">
<legend>Please specify your gender:</legend>
<TextInput
v-model="gender"
type="radio"
description="Male"
name="Gender"
rules="required"
/>
<TextInput
v-model="gender"
type="radio"
description="Female"
name="Gender"
rules="required"
/>
<TextInput
v-model="gender"
type="radio"
description="Unspecified"
name="Gender"
rules="required"
/>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
This is how I'm declaring my input fields in my form.
Here's the definiton of the Input Field.
<template>
<ValidationProvider
tag="div"
:rules="rules"
:name="name"
:vid="vid"
v-slot="{ errors }"
:mode="mode"
>
<label>
{{ description }}
<input :type="type" v-model="currentValue" :value="value" />
</label>
<span>{{ errors[0] }}</span>
</ValidationProvider>
</template>
<script>
import { ValidationProvider } from "vee-validate";
export default {
name: "TextInput",
components: {
ValidationProvider
},
props: {
description: {
type: String,
default: ""
},
value: {
required: true
},
rules: {
type: [String, Object],
default: ""
},
name: {
type: String,
default: ""
},
vid: {
type: String,
default: undefined
},
type: {
type: String,
default: "text"
},
mode: {
type: String,
default: "aggressive"
}
},
data: () => ({
currentValue: ""
}),
watch: {
currentValue(val) {
// allows us to use v-model on our input.
this.$emit("input", val);
}
}
};
</script>
<style></style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
THe only input fields I have a problem with are those radio buttons. Is there something I'm missing?
The easiest way to fix this is to skip putting :value="value" on the input and change your watch like this:
watch: {
//watch for value to change and assign it to our currentValue
value: {
handler(val) {
this.currentValue = val;
},
//this makes it run the handler function on mount in addition to whenever the value changes
immediate:true
},
currentValue(val) {
// allows us to use v-model on our input.
this.$emit("input", val);
}
}
I'm a newbie of Vue, and I'm trying to simply clear the data of input component once I've submitted, but it seems I'm missing something, because since it's parent data is cleared, I still see the filled value of the input component.
Here is a living example.
I've set to the input child component v-model="title" from it's parent wrapper. Once I submit the data to the parent, I call addItem and in the end, I supposed to clear the data model just by clear it this.title = '', but I probably do something wrong on how to bind data from parent to child.
And above the code, starting from the parent component:
<template>
<form #submit="addItem" class="todo-insert">
<input-item
icon="create"
name="title"
placeholder="Add a ToVue item..."
v-model="title"
/>
<button-item tone="confirm" class="todo-insert__action">
Aggiungi
</button-item>
</form>
</template>
<script>
import ButtonItem from '#vue/Form/ButtonItem/ButtonItem.vue'
import InputItem from '#vue/Form/InputItem/InputItem.vue'
import uuid from 'uuid'
export default {
name: 'TodoInsert',
components: {
ButtonItem,
InputItem
},
data () {
return {
title: ''
}
},
methods: {
addItem (e) {
e.preventDefault()
const todo = {
id: uuid.v4(),
isComplete: false,
title: this.title
}
this.$emit('add-todo', todo)
this.title = ''
}
}
}
</script>
<style lang="scss" scoped src="./TodoList.scss"></style>
This is the child input component:
<template lang="html">
<label class="input">
<div v-if="label" class="input__label text-sans text-sans--label">
{{ label }}
</div>
<div class="input__row">
<input
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
<div v-if="placeholderLabel" class="input__placeholder text-sans text-sans--placeholder">
{{ placeholderLabel }}
</div>
<div v-if="icon" class="input__icon-area">
<icon-item
:name="icon"
/>
</div>
</div>
</label>
</template>
<script>
import IconItem from '../../IconItem/IconItem.vue'
export default {
name: 'InputItem',
props: {
autocomplete: {
type: String,
default: 'off'
},
icon: String,
label: String,
name: {
type: String,
default: 'input-text'
},
placeholder: String,
placeholderLabel: String
},
computed: {
hasPlaceholderLabel () {
return this.placeholderLabel ? 'input__field--placeholder-label' : ''
},
isDirty () {
// return this.$attrs.value ? 'input__field--dirty' : ''
return 'input__field--dirty'
}
},
methods: {
updateValue: function (value) {
this.$emit('input', value)
}
},
components: {
IconItem
}
}
</script>
<style lang="scss" src="./InputItem.scss"></style>
What am I missing?
Your child component is bound unidirectionally. It means that it can change the value, but does not receive any update from the parent component. To receive updates, you need to receive the property value in your child:
props: {
value: String
}
Then, you need to pass the value received to the input :
<input
:value="value"
:autocomplete="autocomplete"
:class="[hasPlaceholderLabel, isDirty]"
:name="name"
:placeholder="placeholder"
class="input__field"
type="text"
v-on:input="updateValue($event.target.value)"
v-on:blur="updateValue($event.target.value)"
>
Now the input should update when the parent component changes the value