Search code examples
typescriptvue.jsvee-validate

veevalidate reset form typescript does not reset the form


I have a form with veevalidate. On form submit, the data is emitted to the parent, and then I need to reset the form. Following the veevalidate instructions, it should be possible to reset the form via $refs.

My app is in TypeScript, and following the guides, my component looks like this:

<template>
    <div>
        <ValidationObserver v-slot="{ handleSubmit }" ref="form">
            <form :model="form" @submit.prevent="handleSubmit(onSubmit)" novalidate>
                <a-input-with-validation
                        :placeholder="placeholder"
                        :label="placeholder"
                        auto-complete="off"
                        name="comment"
                        type="text"
                        v-model="form.comment"
                        rules="required"
                />

                <a-button-submit :translation-key="$t('dict.save')"/>
            </form>
        </ValidationObserver>
    </div>
</template>

<script lang="ts">
  import { Component, Prop, Vue } from 'vue-property-decorator'
  import { ValidationObserver, ValidationProvider } from 'vee-validate'
  import { OItemCommentFormChildOutput } from '@/components/organisms/forms/OItemCommentFormInterfaces'
  import AInputWithValidation from '@/components/atoms/inputs/AInputWithValidation.vue'
  import AButtonSubmit from '@/components/atoms/buttons/AButtonSubmit.vue'

  @Component({
    components: {
      AButtonSubmit,
      AInputWithValidation,
      ValidationObserver,
      ValidationProvider
    }
  })
  export default class OItemCommentForm extends Vue {
    @Prop()
    comment?: string
    @Prop({ default: true })
    add!: boolean

    $refs!: {
      form: InstanceType<typeof ValidationObserver>;
    }
    placeholder!: string

    form: OItemCommentFormChildOutput = {
      comment: ''
    }

    mounted () {
      this.$refs.form;
    }

    created () {
      this.placeholder = String(this.add ? this.$t('dict.addComment') : this.$t('dict.editComment'))
      this.form.comment = this.comment || ''
    }

    onSubmit () {
      this.$emit('child-output', this.form as OItemCommentFormChildOutput)
      // this.form.comment = ''
      this.$refs.form.reset()
    }
  }
</script>

The a-input-with-validation component code (from veevalidate):

<template>
    <ValidationProvider
            :vid="vid"
            :name="nameAlt || name"
            :rules="rules"
            v-slot="{ errors, valid }"
    >
        <b-field
                :label="label"
                :prop="name"
                v-bind="$attrs"
                :auto-complete="autoComplete"
                :type="{ 'is-danger': errors[0], 'is-success': valid }"
                :message="errors"
        >
            <b-input
                    :name="name"
                    :type="type"
                    v-bind="$attrs"
                    :placeholder="placeholder"
                    v-model="innerValue"
                    :password-reveal="type === 'password'"
                    novalidate
            />
        </b-field>
    </ValidationProvider>
</template>

<style scoped>
    .AInputWithValidation {}
</style>

<script lang="ts">
  import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
  import { ValidationObserver, ValidationProvider } from 'vee-validate'

  @Component({
    components: {
      ValidationObserver,
      ValidationProvider
    }
  })
  export default class AInputWithValidation extends Vue {
    @Prop({ required: true })
    value!: any
    @Prop({ required: true })
    label!: string
    @Prop({ required: true })
    name!: string
    @Prop({ default: 'off' })
    autoComplete!: string
    @Prop({ default: 'text' })
    type!: string
    @Prop()
    nameAlt?: string
    @Prop()
    placeholder?: string
    @Prop()
    vid?: string
    @Prop()
    rules?: string

    innerValue = ''

    created () {
      if (this.value) {
        this.innerValue = this.value
      }
    }

    @Watch('innerValue')
    innerValueHandle (newVal) {
      this.$emit('child-output', newVal)
      this.$emit('input', newVal)
    }

    @Watch('value')
    valueValueHandle (newVal) {
      this.innerValue = newVal
    }

  }
</script>

The app compiles just fine, so seemingly no TS errors. On submit, the data is emitted correctly. But when I try to call the reset method, nothing happens. The form is not reset, and the input value stays the same. If I also force the comment within the form to an empty string, this triggers the validator to display errors.

How can I reset the form after form submit and clear the contents of the form without triggering the validation errors?

(moving the ref="form" onto the form tag does not do anything either :/)


Solution

  • ValidationObserver's reset() only resets the validation state, not the form fields. While the docs include a form-reset demo that manually clears the fields, you could alternatively invoke HTMLFormElement.reset() on the <form> element to reset the fields to their initial values. The <form> element can be accessed from the SubmitEvent's target:

    export default class OItemCommentForm extends Vue {
    
      async onSubmit(e) {
        // Wait microtick for models to update before resetting validation state
        await this.$nextTick()
        this.$refs.form.reset()
    
        // Reset form in next macrotick
        setTimeout(() => e.target.reset())
      }
    }
    

    The key is to reset the form in the next macrotick (via setTimeout with a zero timeout).

    Edit Reset form after submit