logaretm / vee-validate

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

[Question] Revalidation of empty input where initial validation has passed before #4850

Closed mauserzjeh closed 4 weeks ago

mauserzjeh commented 2 months ago

Scenario:

How can I avoid this? I want to trigger validation only on submit, but if a value was deleted it should retrigger the validation when I click submit again instead of passing

Example:

// main.ts
import { createApp } from 'vue'
import App from '@/App.vue'
import { router } from "@/router"
import { createPinia } from "pinia"
import "@/assets/css/tailwind.css"
import { configure } from 'vee-validate'

configure({
  validateOnBlur: false,
  validateOnModelUpdate: false,
})

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.use(router)
app.mount('#app')
// Settings.vue
<template>
    <Layout>
        <form @submit="onSubmit" class="space-y-8 mt-4">
            <div class="grid gap-2">
                <FormField v-slot="{ componentField, meta }" name="name">
                    <FormItem v-auto-animate>
                        <FormLabel>Name</FormLabel>
                        <FormControl>
                            <Input placeholder="My Workspace" v-bind="componentField"
                                :invalid="meta.validated && !meta.valid" />
                        </FormControl>
                        <FormMessage />
                    </FormItem>
                </FormField>
            </div>
            <div class="grid gap-2">
                <Label>Workspace ID</Label>
                <div class="relative items-center">
                    <Input disabled type="text" class="pr-10" :default-value="currentWorkspaceId" />
                    <span @click="copy(currentWorkspaceId)"
                        class="hover:text-foreground text-muted-foreground hover:cursor-pointer absolute end-0 inset-y-0 flex items-center justify-center px-2">
                        <Copy class="size-6 " />
                    </span>
                </div>
            </div>
            <div class="flex gap-2 justify-end">
                <Button type="submit" :disabled="isLoading">
                    <Loader2 v-if="isLoading" class="mr-2 h-4 w-4 animate-spin" />
                    Save
                </Button>
            </div>
        </form>
    </Layout>
</template>

<script setup lang="ts">
import Layout from './Layout.vue';
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { toTypedSchema } from '@vee-validate/zod';
import { z } from 'zod';
import { useForm } from 'vee-validate';
import { computed, onMounted, ref, watch } from 'vue';
import { Loader2, Copy } from 'lucide-vue-next';
import { vAutoAnimate } from '@formkit/auto-animate/vue'
import { useWorkspaceStore } from '@/stores/workspace';
import { useClipboard } from '@vueuse/core';
import { toast } from 'vue-sonner';
import WorkspaceService from '@/api/gen/WorkspaceService';
import { toastError } from '@/components/ui/sonner';

const isLoading = ref(false)
const workspaceStore = useWorkspaceStore()
const currentWorkspaceId = computed(() => workspaceStore.currentWorkspace?.id || '')
const validationSchema = toTypedSchema(z.object({
    name: z.string({ message: "Workspace name is required" })
}))

const { handleSubmit, setErrors, resetForm } = useForm({
    validationSchema
})

const onSubmit = handleSubmit(async (values) => {
    console.log(values)
})

const { copy, copied } = useClipboard()

watch(copied, (newVal) => {
    if (newVal == true) {
        toast("Workspace ID copied to clipboard!", {
            closeButton: true,
            duration: 1500,
        })
    }
})

onMounted(async () => {
    if (currentWorkspaceId.value === '') {
        return
    }
    const resp = await WorkspaceService.getWorkspace({ id: currentWorkspaceId.value })
    if (!resp.success) {
        toastError(resp.errorMessage)
        return
    }

    resetForm({
        values: {
            name: resp.data.name
        }
    })
})

</script>
Snurppa commented 1 month ago

Hmm. I think I struggled with similar issue today, as I had zod schema with z.string().date() added with some .default date.

After clearing the input it somehow still had that initial default value in state and proceeded with submission although the input was empty!

But in your case I am thinking why do you use "reset" to set new values, which you didn't even initially have.

Wouldn't just setting the field value be better? Though not sure if that makes any difference in the end... useSetFieldValue is available from https://github.com/logaretm/vee-validate/pull/4397 .

logaretm commented 4 weeks ago

Can you please add a minimal reproduction?

@Snurppa If you provide defaults to the schema it will stay there since it will always be available for the validators. Usually using default implies a field is optional. I don't think schema validators offer a way to not use the defaults defined with the schema, but at the same time that may not be an intended behavior to schema users if we just ignore defaults.