Repository: logaretm/vee-validate (ts)

Issue:
Switching between fields with same name causes validation field value to be empty
### What happened?

Basically there is a case where switching between two components which use `useField` with same `name` will cause the latter's `field.value` to become undefined. It breaks certain cases like for example
```
<template v-if="value === 1">
    <MyInput name="test" />
</template>
<template v-if="value === 2">
    <MyInput name="test">
</template>
```
and also hot reloading seem to be affected by this to some degree.
From what I've checked it seems that the culprit may be the fact that fields are "unregistered" asynchronously in `nextTick` which causes flow like this:
1. first component gets rendered and it's form value is set 
2. first component gets unmounted and "destroy" of field in form is scheduled to next tick
3. second component gets rendered and it's set properly
4. after a brief moment (next tick) the "destroy" runs making `field.value` in second component equal to `undefined`

I believe the solution could be to check if `UNSET_BATCH` contains given path when creating new component and eventually remove this path from this array, so scheduled "destroy" would not happen on this field.

Given two examples:
https://stackblitz.com/edit/vue-3-veevalidate-form-validation-example-sahnmn?file=src%2Fvee-validate.esm.js,package.json,src%2FField.vue,src%2FApp.vue
This one seems to work properly (4.5.1) and field value stays as "test".

Here:
https://stackblitz.com/edit/vue-3-veevalidate-form-validation-example-1dlyi1?file=src%2Fvee-validate.esm.js,package.json,src%2FField.vue,src%2FApp.vue
Field value will become undefined.

The only workaround (which seem to not be ideal) is something I created in second example:
![image](https://github.com/logaretm/vee-validate/assets/3301242/0bc4fb5f-7782-41e0-b003-0603bd2f0311)
If return is commented/removed then it will evaluate AFTER the scheduled removal and "overwrite" the undefined value.
### Reproduction steps

Check both examples and look for "Field value"

### Version

Vue.js 3.x and vee-validate 4.x

### What browsers are you seeing the problem on?

- [ ] Firefox
- [X] Chrome
- [ ] Safari
- [ ] Microsoft Edge

### Relevant log output

_No response_

### Demo link

provided in description

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](CONDUCT.md)

Pull request:
fix: unset paths
not removing paths which were re-created between unset schedule and dispatch

✔ __Issues affected__
closes #4603 
 


Patch:
diff --git a/packages/vee-validate/src/useForm.ts b/packages/vee-validate/src/useForm.ts
index e6092664..25520284 100644
--- a/packages/vee-validate/src/useForm.ts
+++ b/packages/vee-validate/src/useForm.ts
@@ -289,6 +289,12 @@ export function useForm<
 
     const currentValue = computed(() => getFromPath(formValues, toValue(path)));
     const pathValue = toValue(path);
+
+    const unsetBatchIndex = UNSET_BATCH.findIndex(_path => _path === pathValue);
+    if (unsetBatchIndex !== -1) {
+      UNSET_BATCH.splice(unsetBatchIndex, 1);
+    }
+
     const isRequired = computed(() => {
       if (isTypedSchema(schema)) {
         return (schema as TypedSchema).describe?.(toValue(path)).required ?? false;


Test patch:
diff --git a/packages/vee-validate/tests/useField.spec.ts b/packages/vee-validate/tests/useField.spec.ts
index 36aa8031..e3156cad 100644
--- a/packages/vee-validate/tests/useField.spec.ts
+++ b/packages/vee-validate/tests/useField.spec.ts
@@ -1009,4 +1009,55 @@ describe('useField()', () => {
     await flushPromises();
     expect(field.errors.value).toHaveLength(0);
   });
+
+  // #4603
+  test('should not remove field value if field with same path was created between scheduling and execution of previous field unset operation', async () => {
+    vi.useFakeTimers();
+    let form!: FormContext;
+    mountWithHoc({
+      setup() {
+        form = useForm();
+        const toggle = ref(false);
+        const value = ref('');
+
+        onMounted(async () => {
+          await new Promise(resolve => {
+            setTimeout(() => resolve(null), 1000);
+          });
+
+          toggle.value = true;
+          value.value = 'test';
+        });
+        return { form, toggle, value };
+      },
+      template: `
+      <template v-if="!toggle">
+        <CustomField name="field" :model-value="value" />
+      </template>
+      <template v-if="toggle">
+        <CustomField name="field" :model-value="value" />
+      </template>
+      `,
+      components: {
+        CustomField: {
+          props: {
+            name: String,
+            modelValue: String,
+          },
+          setup(props: any) {
+            useField(props.name, undefined, {
+              initialValue: props.modelValue,
+            });
+          },
+          template: `<input type="text" :value="modelValue" :name="name" />`,
+        },
+      },
+    });
+
+    await flushPromises();
+    vi.advanceTimersByTime(1000);
+    await flushPromises();
+
+    expect(form.values.field).toEqual('test');
+  });
 });