vee-validate
vee-validate copied to clipboard
The best way to omit `useField`?
I wish this code will work so we can omit writing the code for useField
for each field one by one.
<template>
<input v-model="formValues.email" placeholder="email" />
<pre v-text="JSON.stringify(errors)"></pre>
</template>
<script>
import { useForm } from "vee-validate";
import { string, object } from "yup";
import { reactive } from "vue";
export default {
name: "App",
setup() {
const validationSchema = object({
email: string().required().email(),
password: string().required(),
});
const formValues = reactive(getTheDataFromApi());
const { errors } = useForm({
validationSchema,
initialValues: formValues,
});
return {
errors,
formValues,
};
},
};
</script>
This is a simple validation schema, imagine if you have tons of fields, that will be a nightmare to use useField
for each field, things even goes more complex when the schema is a nested one.
Sure I could do this by using the Field
component with the v-model
, for example
<Field type="email" v-model="formValues.email" />
, but I think it'll be more convenient if I could do this with the composition API.
If the initialValues
accepts reactive value, why not just use it as the returned value of useField
? I think this could be done by just watching the initialValues
and validate the form when it changes. What do you think?
useField does more than just holding the value, it informs the vee-validate form what kind of field it is and the path it occupies. In your example mutating the form values makes a lot of sense since it is a simple flat object. But consider a form with complex nested paths like arays:
{
name: '',
array: [{ test: '' }]
}
In the previous example, it is hard to tell which part of the array is the actual field, Is it the array as a whole (field emits an array value) or is it a single property on one of its objects, or is it an element of that array (field emits an object value). Telling the difference doesn't matter in terms of validation but it is critical in terms of meta flags.
The problem with direct mutations is that it is impossible to figure out the user intent from a simple set
operation. Let's say you do something like this:
const { values } = useForm();
function reset() {
values.name = '';
values.array = [];
}
function populate() {
values.name = 'something';
values.array = [...]
}
There is no inherent difference between the two functions here, and vee-validate cannot tell what kind of mutation it was to optimize the other aspects like errors, meta flags, and more. Such an open mutable API is dangerous to allow, a good example of this was in v3 of vee-validate where it piggybacked on the v-model
you already have and to reset the form you always had to wait for a tick or two after changing your field values.
If the initialValues accepts reactive value, why not just use it as the returned value of useField?
Because the initial value may as well be a read-only value so it cannot be mutated and there is no way to tell that a Vue reactive or ref is read-only. Reactive here means it could be either: reactive proxy, ref, computed ref, shallowRef, readonly proxy, and customRef. The computed/customRef refs are impossible to tell if it is immutable or not so vee-validate cannot really assume that it is.
imagine if you have tons of fields
How do you plan to organize such fields? are they components that you created (if so then calling useField isn't really a deal-breaker here)? If they are 3rd party then I agree it would be hard to wrap each and every one of them with useField
.
Having said all of this, I'm not against having a simple mutable form value here. I will try to think of a way to allow v-model
to work on form values without compromising the existing features.
This actually works well in array fields in the v4.6
in #3618. So doing the same thing for form values could be tricky, but could be exposed under a different API that doesn't work with useField
and mainly focuses on the model
aspect. Like useFormModel
or useFastForm
as some libraries do that kind of variations for this use-case.
Thanks @logaretm , I really appreciate that you invented such an awesome plugin for vue 3. In the past few months I really enjoyed it with using the components like <Filed>
or <Form>
, but when I recently started a new project, which use a third party UI framework comparing with where previous project was using just the vanilla form elements, I had a hard time to wrap the input components from third party UI framework into tons of <Field>
elements.
In my usecase, I have a layer of model, which like a ORM, so for example:
const userProfle = reactive(new UserProfleModel(await getDataFromApi()))
const onSubmit = async ()=> {
await userProfle.save()
alert('user profile saved!')
}
<Form @submit="onSubmit" :validation-schema="schema">
<Field v-model="userProfile.email" name="email" />
<Field v-model="userProfile.firstName" name="firstName" />
<Field v-model="userProfile.lastName" name="lastName" />
<Field v-model="userProfile.company.name" name="company.name" />
<Field v-model="userProfile.company.address" name="company.address" />
</Form>
as you can see I'm not even using the form values which passed over by vee-validate through the onSubmit
, to me, the reactive two way binding is more easier to understand and far more less code to write and manage instead of using the useField
and useForm
.
But this time, I'm using a third party UI framework, so I have to wrap the form elements into the <Field>
like
<Form @submit="onSubmit" :validation-schema="schema">
<Field v-model="userProfile.email" name="userProfile.email">
<MyInput v-model="userProfile.email"/>
</Field>
</Form>
which looks horrible and hard to understand. I really wish I could just do something like this with the composition API.
<Form @submit="onSubmit" :validation-schema="schema">
<MyInput v-model="userProfile.email"/>
</Form>
so I could just send the form values to the server as simple as this:
const onSubmit = async ()=> {
await userProfle.save()
alert('user profile saved!')
}
as you said, new API like useFormModel
is a good idea I think, that way the initialValues
should be ignored, and the input of useFormModel
should be a reactive object, the return value is void
or just the passed reactive object itself(in terms of naming maybe instead of useFormModel
, bindFormModel
is a better name?).
so the pervious example could be written like this which is way more easier to work with.
useForm({
validationSchema,
});
const userProfile = reactive(getTheDataFromApi())
useFormModel(userProfile)
by the way, if the field of model is type of array, I use a simple useArrayField
composable to get the add
and remove
functions.
const { add, remove } = useArrayField(userProfile, 'friends')
Anyway, I'm really looking forward to have this kind of new API 😀
@yaquawa have you already tried the following example? I can create my own input component with whatever the syntax/code I would like to embed but using useField
just to parse the initial values and capture any errors
.
https://codesandbox.io/s/custom-text-input-with-vee-validate-v4-8k9gy?from-embed=&file=/src/components/TextInput.vue
@jimbatamang I guess you just missed my point, the point is that how to make the most out of vee-validate in terms of it's composition APIs. Currently, it's too verbose to use the composition APIs.
This took a while but I merged a recent new feature that is similar to what is discussed here in https://github.com/logaretm/vee-validate/commit/26c828e21495c485d489ea1319575d9b5c271801
It mainly addresses this:
Currently, it's too verbose to use the composition APIs.
This is valid if you are only using useField
as a model creator, which while is fine is not encouraged like I mentioned. Still it would be nice to have something that caters to the model users specific case.
So this is what the API looks like on main branch:
const { useFieldModel } = useForm(...);
// single field
const email = useFieldModel('email');
// multiple fields
const [email, name, password] = useFieldModel(['email', 'name', 'password']);
It supports nested fields as well but you should be careful when setting paths as you can easily create a model that changes an entire path like an array of objects.
The downside of this API is you lose any information related to meta
for the individual fields, but you can still rely on the form's version of it. Also, validation will become aggressive since it has no field instances created with useField
.
This will be tagged in 4.6 once I implement a few other changes that tie in nicely with this one.
Hi @logaretm , nice work! Thank you for your effort for this enhancement, I'lll take a look at it later on.
This took a while but I merged a recent new feature that is similar to what is discussed here in 26c828e
It mainly addresses this:
Currently, it's too verbose to use the composition APIs.
This is valid if you are only using
useField
as a model creator, which while is fine is not encouraged like I mentioned. Still it would be nice to have something that caters to the model users specific case.So this is what the API looks like on main branch:
const { useFieldModel } = useForm(...); // single field const email = useFieldModel('email'); // multiple fields const [email, name, password] = useFieldModel(['email', 'name', 'password']);
It supports nested fields as well but you should be careful when setting paths as you can easily create a model that changes an entire path like an array of objects.
The downside of this API is you lose any information related to
meta
for the individual fields, but you can still rely on the form's version of it. Also, validation will become aggressive since it has no field instances created withuseField
.This will be tagged in 4.6 once I implement a few other changes that tie in nicely with this one.
I just started using vee-fleet v4 and was precisely looking for a way to avoid the validation to happen on my fields created with useFieldModel, would like to be able to either on focus or on blur to have those messages shown. So I guess Ill take a look for 4.6.
Thanks for the hard work.
@logaretm Can't we keep a dirty state parallely ourselves for useFieldModel and only mark fields dirty if the user has mutated value ?
4.9 introduced a new helper relevant to this.
With that, you can avoid wrapping fields with Field
or useField
especially with 3rd party components. However, it still means you need to call defineComponentBinds
for every field you plan to use. This is a better alternative than useFieldModel
because it also creates and maintains meta
object.
With this, I don't think there is anything else to add to fully fix this. As always suggestions are welcome.