vee-validate
vee-validate copied to clipboard
Debounce validation on Field / useField
It seems that v2 version had an option to debounce triggering validation on input. However I cannot find any way to debounce in vee-validate v4 (vue 3). It would be great if we could set time in miliseconds that would debounce validation. In this way, validation would wait till user finish writing (ex 500ms). Input blur solution does not satisfy my needs (we have to trigger validation while user has still focus in input, however it cannot be immediate).
This was requested a few times and I think it is straightforward to add in vee-validate, so I will mark it as an enhancement and decide if it should be added or not.
Here is a quick snippet if you are using the composition API which is easiest to implement:
import { useField } from 'vee-validate';
import { debounce } from 'lodash-es';
const { value, handleChange } = useField('some field', 'rules', {
validateOnValueUpdate: false,
});
const debouncedValidation = debounce(validate, 400);
const onInput = (event) => {
handleChange(event, false);
debouncedValidation();
};
``
Then bind the `onInput` to your desired input event on your input, be it `@input` or `@update:modelValue` if it is a component.
HI !
In 4.9.3, after the fix on handleChange (https://github.com/logaretm/vee-validate/issues/4251)not validating by default, the validation rules are called before the debounced handleChange method even with validateOnValueUpdate: false
It means my field meta flag and errors get update immediatly and then, after the debounce delay get updated again.
Could you look into that ?
Here the sample code I use :
const {
value: inputValue,
errors,
handleChange,
handleBlur,
meta,
} = useField<string>(name, rules, {
initialValue: props.modelValue,
label: labelInError.value ? labelInError : label,
validateOnValueUpdate: false,
});
const debouncedHandleChange = debounce(v => {
// Here it's called after 3sec but the validation already occured
return handleChange(v);
}, 3000);
const localValue = computed<string>({
get() {
return inputValue.value;
},
set(value: string) {
emit('update:modelValue', value);
// validation occured here before the call to handle change
debouncedHandleChange(value);
},
});
Edit: checking with debugger my rule is called twice, before beeing called by handleChange
Edit2: rolling back to 4.7.4 fix the issues and i do not event need the validateOnValueUpdate part.
@xontik sure, I will look into it later today. validateOnValueUpdate
was always there, it worked differently.
Another possible reason is useField
does listen for modelValue
on your component so can you try setting syncVModel
to false and see if it works for you?
@logaretm yes searching in the code that's what i concluded, with both validateOnValueUpdate false and syncVModel false it works. Is it the recommended way to do it in 4.9 ?
Depending on what your component is doing, each option controls a different aspect:
- if you are syncing the input value directly with
v-model=inputValue
then you have to turn offvalidateOnValueUpdate
. - if you are managing the
v-model
updates manually (and you are in the example by debouncing it) then yes, you should turnsyncVModel
off.
So in your case, maybe the combo is required but I'm wondering why it worked before without setting those options. Possibly if you added v-model
support recently then it caused this, because useField
detects if modelValue
is defined as a prop or not to turn syncVModel
on or off automatically if not specified.
I'm also trying to add debounce feature to useField but I can't make it work because validation rules are always executed, regardless of validateOnValueUpdate
or shouldValidate
parameter from handleChange
method.
I tried setting syncVModel=false
just to see if it would work, but it didn't in my case. I also tried rolling back to 4.7.4 and I still can't make it work. Here's what I could figure out so far.
To be able to make debouncing feature available to anyone using my components I wrapped useField
method with my own:
export function useField<TValue = unknown>(
name: MaybeRef<string>,
rules?: ValidationRules,
opts?: Partial<ValidationFieldOptions<TValue>>): ValidationField<TValue> {
const debounceOption = unref(opts?.debounce);
let debounceMilliseconds = 0;
if (debounceOption === true) {
// Default debouncing is 250ms (when debounceOption = true)
debounceMilliseconds = 250;
} else if (Number.isFinite(debounceOption)) {
// If debounceOption is a number, then we use that as the debouncing delay
debounceMilliseconds = Number(debounceOption);
}
// If debounce was not configured, return the original useField from vee-validate
if (debounceMilliseconds <= 0) {
return veeUseField(name, rules, opts);
}
// If debounce was configured, then we force `validateOnValueUpdate = false`
// since we'll be handling the call to validate ourselves.
opts = opts ?? {};
opts.validateOnValueUpdate = false;
const field = veeUseField(name, rules, opts);
// Setup the debouncedValidation callback
const debouncedValidation = debounce(field.validate, debounceMilliseconds);
// Create our custom handleChange method because we want to:
// 1. Update the value without checking for validations rules (we use shouldValidate = false for that)
// 2. Call debouncedValidation method that will take care of respecting the configure debouncing delay
// and then checking validation rules and updating field.meta
const handleChange = field.handleChange;
field.handleChange = (function(e: unknown, shouldValidate?: boolean) {
handleChange(e, false);
if (shouldValidate !== false) {
debouncedValidation();
}
}).bind(field);
return field;
}
The above does not work because, as soon as handleChange(e, false)
is called, validation rules are executed and field.meta.valid is updated. This behavior happens even after I explicitly set validateOnValueUpdate = false
and shouldValidate = false
I understand that #4251 had some issues with field.meta.valid not being updated when it should, but the current behavior is rather strange: we have 2 parameters (validateOnValueUpdate
and shouldValidate
) that say that validation shouldn't happen, but, regardless of what is set in those parameters, validation rules are executed anyway. I think its reasonable to expect that users will understand that field.meta won't be updated if they use shouldValidate = false
.
As far as I could track this down, the issue is caused by this if statement. It seems to me that changing setValue
method to:
function setValue(newValue: TValue, shouldValidate = true) {
value.value = newValue;
if (shouldValidate) {
validateWithStateMutation();
}
}
would make everything work as expected, without breaking the fix that was made for #4251. Am I missing something about this change? If I am, how can I add validation debouncing when validation is always executed real-time with no way to disable it?
Also having trouble getting this working via the options API. validateName
issues an async call and is fired on every input despite the validateOnModelUpdate
and related props set to false
. It does not matter if I have the onInput
handler there or not
<Field
name="name"
v-model="form.name"
:rules="validateName"
:validateOnModelUpdate="false"
:validateOnChange="false"
:syncVModel="false"
v-slot="{ field, handleChange, validate }"
>
<input
v-bind="field"
type="text"
id="input-name"
required
autocomplete="off"
:label="$l('Name')"
:placeholder="$l('Enter name')"
@input="onInput($event, handleChange, validate)"
>
</Field>
---
onInput(e, handleChange, validate) {
handleChange(e, false)
this.debounceValidate(validate)
},
debounceValidate(fn) {
debounce(fn, 500)
}
I have hit this issue i am using vee validate with element-plus and the input fields feels a bit laggy
I would love to open a PR for this
You check the latest version ton's of things have changed.
Implement debounce validation
- implement closure:
function debounceX() {
let event = null
const debounceValidation = debounce(() => handleChange(event), 2000)
return ($event) => {
event = $event
debounceValidation()
}
}
const onInput = debounceX()
- bind event
<input :value=value @input="onInput"/>