logaretm / vee-validate

✅ Painless Vue forms
https://vee-validate.logaretm.com/v4
MIT License
10.79k stars 1.26k forks source link

Composition API: Allow usefield() to accept an existing reactive value #3408

Closed gagarine closed 3 years ago

gagarine commented 3 years ago

If you use vuex or pinna as a store, you want to use your store value directly. Those a reactive value. Yet useField() create a reactive value but you cannot pass an existing one to it.

import { useField } from 'vee-validate';
import {useSubscriberStore} from "../store/subscriber.js";

setup(props, context) {
    const { handleSubmit } = useForm()
    const subscriberStore = useSubscriberStore()

    // I want to use subscriberStore.firstname here in useField witch is a reactive value already
    let { errorMessage: firstnameErrorMessage, value: firstname, name: firstnameName } = useField('firstnameName');

    return {errorMessage, firstname, firstnameName}

}

Their is related questions on stack:

gagarine commented 3 years ago

A solution is to use watcher, but it seem strange to create a reactive value has we already have one.

import { useField } from 'vee-validate';
import {useSubscriberStore} from "../store/subscriber.js";

setup(props, context) {
    const { handleSubmit } = useForm()
    const subscriberStore = useSubscriberStore()

    // I want to use subscriberStore.firstname here in useField witch is a reactive value already
    let { errorMessage: firstnameErrorMessage, value: firstname, name: firstnameName } = useField('firstnameName');

    // Apply the store change to the vee-validate value
    watchEffect(() => {
      firstname.value = subscriberStore.firstName
    })

    return {errorMessage, subscriberStore.firstName, firstnameName}

}
logaretm commented 3 years ago

This has come up a couple of times before, I will explain the caveats here:

While having a watcher may seem the way at first glance you have to remember that vee-validate needs two-way access to that reactive value, because vee-validate doesn't just read values, it also writes them. so the writability of a reactive reference cannot be guaranteed.

To clarify, you may pass reactive values using these methods:

AFAIK there is no way to tell if a reactive ref is writable or not without actually trying.

So instead of electing to provide an API that "sometimes" works, I prefer to leave it to userland. Another justification of that decision is that vee-validate now handles the value tracking aspect, so doing it twice is kind of redundant, depends on your use case of course.

When it comes to stores, I advise against tracking form values using stores. In my eyes, stores are meant for global sharable data, and most forms don't share their state around the entire app. Also more often than not, forms have an ephemeral state (non-persistable).

You can pass a reactive value to useField using the initialValue option but it will just clone it, and it won't sync it for those reasons.

gagarine commented 3 years ago

Thanks for your reply.

vee-validate doesn't just read values, it also writes them.

Mmmh I didn't catch that in the doc.

When it comes to stores, I advise against tracking form values using stores. In my eyes, stores are meant for global sharable data, and most forms don't share their state around the entire app. Also more often than not, forms have an ephemeral state (non-persistable).

Use case could be multi-step form or form that allow to manipulate an object that you save from time to time. So exactly when you want multiple form to keep and share their state.

gagarine commented 3 years ago

I'm coming back to this bug, I still don't understand why you do not accept reactive value.

AFAIK there is no way to tell if a reactive ref is writable or not without actually trying.

This could be documented: "only accept writable reactive value created with ref()". And when you write to the value you can use a try and catch and generate a nice exception that explain that the passed reactive value is not writable.

logaretm commented 3 years ago

It's not a bug, this is intended. The behavior you want existed in v4 alpha releases, but was removed due to not being able to cover all grounds which is in my opinion makes it easier to explain and to work with.

I will elaborate more on other issues with allowing this:

Not all values can be set as direct assignments like your case. For example, an array created with a computed ref without a setter can still be modified with push and other mutation array methods, but only in some cases. Same issue with objects or non-primitive refs.

So that again brings us to the issue of "an API that sometimes works" and I don't like to have something obscure like that around with so many caveats and edge cases.

try and catch and generate a nice exception that explains...

Except AFAIK Vue doesn't throw, it just warns. So we cannot tell if the value changed or not unless it inspects the value before and after the setter is run. And even then, async setters won't work well with that behavior because they will be detected as "read-only" when they are not.

gagarine commented 3 years ago

Thanks for the detailed explanations. Always nice to know why things are like they are.

llacina commented 1 year ago

@gagarine I had similar problem and I found solution without watchEffect - If you want it write me here.

joaopedrodcf commented 2 weeks ago

A solution is to use watcher, but it seem strange to create a reactive value has we already have one.

import { useField } from 'vee-validate';
import {useSubscriberStore} from "../store/subscriber.js";

setup(props, context) {
    const { handleSubmit } = useForm()
    const subscriberStore = useSubscriberStore()

    // I want to use subscriberStore.firstname here in useField witch is a reactive value already
    let { errorMessage: firstnameErrorMessage, value: firstname, name: firstnameName } = useField('firstnameName');

    // Apply the store change to the vee-validate value
    watchEffect(() => {
      firstname.value = subscriberStore.firstName
    })

    return {errorMessage, subscriberStore.firstName, firstnameName}

}

nice solution ! 🙏

joaopedrodcf commented 2 weeks ago

@gagarine I had similar problem and I found solution without watchEffect - If you want it write me here.

How did you manage it ?