vee-validate
vee-validate copied to clipboard
Sample implementation for TW-Elements with Yup
What happened?
HI,
I just exploring on using this package with TW-Elements but it seems like there's no sample in the docs. Although the in-house validation on TW-Elements with their forms and other form elements is good but I need a realtime approach like in the previous versions where you can just wrap the form field element with validation-provider
tag
I have a registration form using Nuxt 3, vee-validate and Yup where everytime a user interact with the form fields, it should be automatically validate the focused fields, and if the form fields is valid, that's the time the register button will be activated, hence, it should fill all the fields that can passed by the validation.
I have an idea on re-initiate the value of data-te-validated
form attribute everytime from false to true when the user interact with the form but it seems like a bad logic, since I'm relying on the said form attribute.
Any workaround based on my sample code is much appreciated. And I hope the docs with update to provide at least a simple implementation on form validation via TW-Elements.
Thank you so much.
<script setup>
import { onMounted } from 'vue'
import { useForm } from 'vee-validate'
import { isEmpty } from 'lodash'
import * as yup from 'yup'
const emit = defineEmits(['register', 'clearRegisterError'])
const props = defineProps({
formErrors: {
type: Object,
default: null
}
})
const { meta, errors, setErrors, isSubmitting, isValidating, handleSubmit, defineField } = useForm({
validationSchema: yup.object({
name: yup.string().required('Name field is required'),
email: yup.string().email('Email Address must be email').required('Email Address is required'),
password: yup.string().min(6, 'Password should be longer than 6 characters').required('Password field is required'),
password_confirmation: yup.string()
.required('Confirm Password field is required')
.oneOf([yup.ref('password')], 'Passwords do not match'),
})
})
const [name, nameProps] = defineField('name');
const [email, emailProps] = defineField('email');
const [password, passwordProps] = defineField('password');
const [password_confirmation, passwordConfirmationProps] = defineField('password_confirmation');
const onSubmit = handleSubmit(values => {
emit('register', values)
})
const submitDisabled = computed(() => isSubmitting.value || isValidating.value || !meta.value.valid || !!props.value?.formErrors)
function handleClearError (field) {
emit('clearRegisterError', field)
}
setErrors({
name: props.value?.formErrors?.name,
email: props.value?.formErrors?.email,
password: props.value?.formErrors?.password,
password_confirmation: props.value?.formErrors?.password_confirmation,
})
onMounted(async () => {
const {
Input,
Ripple,
Validation,
initTE,
} = await import('tw-elements')
initTE({ Input, Ripple, Validation }, { allowReinits: true })
})
</script>
<template>
<form
data-te-validation-init
data-te-class-notch-leading-valid="border-[#e5e5e5] dark:border-[#e5e5e5] group-data-[te-input-focused]:shadow-[-1px_0_0_#3B71CA,_0_1px_0_0_#3B71CA,_0_-1px_0_0_#3B71CA] group-data-[te-input-focused]:border-[#3B71CA] !text-[#e5e5e5] !dark:text-primary placeholder:text-neutral-200 dark:placeholder:text-neutral-200 [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0 peer-focus:text-primary peer-data-[te-input-state-active]:-translate-y-[1.15rem] peer-data-[te-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
data-te-class-notch-middle-valid="border-[#e5e5e5] dark:border-[#e5e5e5] group-data-[te-input-focused]:shadow-[0_1px_0_0_#3B71CA] group-data-[te-input-focused]:border-[#3B71CA] !text-[#e5e5e5] !dark:text-primary placeholder:text-neutral-200 dark:placeholder:text-neutral-200 [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0 peer-focus:text-primary peer-data-[te-input-state-active]:-translate-y-[1.15rem] peer-data-[te-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
data-te-class-notch-trailing-valid="border-[#e5e5e5] dark:border-[#e5e5e5] group-data-[te-input-focused]:shadow-[1px_0_0_#3B71CA,_0_-1px_0_0_#3B71CA,_0_1px_0_0_#3B71CA] group-data-[te-input-focused]:border-[#3B71CA] !text-[#e5e5e5] !dark:text-primary placeholder:text-neutral-200 placeholder:text-neutral-200 dark:placeholder:text-neutral-200 [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0 peer-focus:text-primary peer-data-[te-input-state-active]:-translate-y-[1.15rem] peer-data-[te-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
data-te-class-label-valid="!data-[te-input-state-active]:text-[#e5e5e5] !peer-focus-data-[te-input-state-active]:text-primary !peer-data-[te-input-state-active]:text-[#e5e5e5] !peer-focus-data-[te-input-state-active]:text-primary"
data-te-class-invalid-feedback="absolute top-full left-0 m-1 w-auto text-sm text-[#dc4c64] animate-[fade-in_0.3s_both] capitalize-first-letter"
data-te-valid-feedback=" "
data-te-active-validation="true"
:data-te-validated="true"
@submit.prevent="onSubmit"
>
<div
class="relative mb-8"
data-te-input-wrapper-init
data-te-validate="input"
:data-te-invalid-feedback="errors?.name || props.value?.formErrors?.name"
:data-te-validation-state="errors?.name || props.value?.formErrors?.name ? 'invalid' : 'valid'"
>
<input
v-model="name"
v-bind="nameProps"
type="text"
class="peer block min-h-[auto] w-full rounded border-0 bg-transparent px-3 py-[0.32rem] leading-[2.15] outline-none transition-all duration-200 ease-linear focus:placeholder:opacity-100 data-[te-input-state-active]:placeholder:opacity-100 motion-reduce:transition-none dark:text-neutral-200 dark:placeholder:text-neutral-200 [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0"
name="name"
placeholder="Name"
@input.once="handleClearError('name')"
/>
<label
for="name"
class="pointer-events-none absolute left-3 top-0 mb-0 max-w-[90%] origin-[0_0] truncate pt-[0.37rem] leading-[2.15] text-neutral-500 transition-all duration-200 ease-out peer-focus:-translate-y-[1.15rem] peer-focus:scale-[0.8] peer-focus:text-primary peer-data-[te-input-state-active]:-translate-y-[1.15rem] peer-data-[te-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
>
Name
</label>
</div>
<div
class="relative mb-8"
data-te-input-wrapper-init
data-te-validate="input"
:data-te-invalid-feedback="errors?.email || props.value?.formErrors?.email"
:data-te-validation-state="errors?.email || props.value?.formErrors?.email ? 'invalid' : 'valid'"
>
<input
v-model="email"
v-bind="emailProps"
type="text"
class="peer block min-h-[auto] w-full rounded border-0 bg-transparent px-3 py-[0.32rem] leading-[2.15] outline-none transition-all duration-200 ease-linear focus:placeholder:opacity-100 data-[te-input-state-active]:placeholder:opacity-100 motion-reduce:transition-none dark:text-neutral-200 dark:placeholder:text-neutral-200 [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0"
name="email"
placeholder="Email address"
@input.once="handleClearError('email')"
/>
<label
for="email"
class="pointer-events-none absolute left-3 top-0 mb-0 max-w-[90%] origin-[0_0] truncate pt-[0.37rem] leading-[2.15] text-neutral-500 transition-all duration-200 ease-out peer-focus:-translate-y-[1.15rem] peer-focus:scale-[0.8] peer-focus:text-primary peer-data-[te-input-state-active]:-translate-y-[1.15rem] peer-data-[te-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
>
Email Address
</label>
</div>
<div
class="relative mb-8"
data-te-input-wrapper-init
data-te-validate="input"
:data-te-invalid-feedback="errors?.password || props.value?.formErrors?.password"
:data-te-validation-state="errors?.password || props.value?.formErrors?.password ? 'invalid' : 'valid'"
>
<input
v-model="password"
v-bind="passwordProps"
type="password"
class="peer block min-h-[auto] w-full rounded border-0 bg-transparent px-3 py-[0.32rem] leading-[2.15] outline-none transition-all duration-200 ease-linear focus:placeholder:opacity-100 data-[te-input-state-active]:placeholder:opacity-100 motion-reduce:transition-none dark:text-neutral-200 dark:placeholder:text-neutral-200 [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0"
id="password"
placeholder="Password"
@input.once="handleClearError('password')"
/>
<label
for="password"
class="pointer-events-none absolute left-3 top-0 mb-0 max-w-[90%] origin-[0_0] truncate pt-[0.37rem] leading-[2.15] text-neutral-500 transition-all duration-200 ease-out peer-focus:-translate-y-[1.15rem] peer-focus:scale-[0.8] peer-focus:text-primary peer-data-[te-input-state-active]:-translate-y-[1.15rem] peer-data-[te-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
>
Password
</label>
</div>
<div
class="relative mb-8"
data-te-input-wrapper-init
data-te-validate="input"
:data-te-invalid-feedback="errors?.password_confirmation || props.value?.formErrors?.password_confirmation"
:data-te-validation-state="errors?.password_confirmation || props.value?.formErrors?.password_confirmation ? 'invalid' : 'valid'"
>
<input
v-model="password_confirmation"
v-bind="passwordConfirmationProps"
type="password"
class="peer block min-h-[auto] w-full rounded border-0 bg-transparent px-3 py-[0.32rem] leading-[2.15] outline-none transition-all duration-200 ease-linear focus:placeholder:opacity-100 data-[te-input-state-active]:placeholder:opacity-100 motion-reduce:transition-none dark:text-neutral-200 dark:placeholder:text-neutral-200 [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0"
name="passwordConfirmation"
placeholder="Confirm Password"
@input.once="handleClearError('password_confirmation')"
/>
<label
for="passwordConfirmation"
class="pointer-events-none absolute left-3 top-0 mb-0 max-w-[90%] origin-[0_0] truncate pt-[0.37rem] leading-[2.15] text-neutral-500 transition-all duration-200 ease-out peer-focus:-translate-y-[1.15rem] peer-focus:scale-[0.8] peer-focus:text-primary peer-data-[te-input-state-active]:-translate-y-[1.15rem] peer-data-[te-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
>
Confirm Password
</label>
</div>
<button
type="submit"
class="inline-block w-full rounded bg-primary px-7 pb-2.5 pt-3 text-sm font-medium uppercase leading-normal text-white shadow-[0_4px_9px_-4px_#3b71ca] transition duration-150 ease-in-out hover:bg-primary-600 hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:bg-primary-600 focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:outline-none focus:ring-0 active:bg-primary-700 active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] dark:shadow-[0_4px_9px_-4px_rgba(59,113,202,0.5)] dark:hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)]"
data-te-submit-btn-ref
:class="submitDisabled ? 'pointer-events-none disabled:opacity-70' : ''"
:disabled="submitDisabled"
>
Register
</button>
</form>
</template>
Reproduction steps
- Create register form component
- Install and use vee-validate with yup
- Setup form validation based on the documentation with yup implementation
- Apply learning on integrating the validation on TW-Elements form components
- Try to check the behavior of the validation in every form field but only one field(depends on the user's selected field) will trigger the validation, the rest is not showing validation although in the component data the validation error message is showing, it needs to click the button to trigger TW-Element's validation in order to display all the validation set from vee-validate
Version
Vue.js 3.x and vee-validate 4.x
What browsers are you seeing the problem on?
- [ ] Firefox
- [ ] Chrome
- [ ] Safari
- [X] Microsoft Edge
Relevant log output
No response
Demo link
none
Code of Conduct
- [X] I agree to follow this project's Code of Conduct