3

I need to handle an event in a child component; check for a certain condition; if true, emit the "submit" event back to the parent so it's event handler will run.

The example below fires the parent's event handler once using Vue.js 2.6.11 (replacing "vue" with "@vue/composition-api"). With 3.0.0-rc.5, it fires twice. Wondering if this is an intentional change, a bug, or me.

App.vue:

<template lang="pug">
#app
  .container
    h1 Form Experiment
    FormGroup(@submit='onSubmit')
      button.btn.btn-primary(type='submit') Submit
</template>

<script lang="ts">
import { defineComponent } from "vue"
import FormGroup from "@/components/FormGroup.vue"

export default defineComponent({
  name: "App",
  components: {
    FormGroup,
  },
  setup() {
    const onSubmit = (): void => {
      alert("Parent event handler")
    }
    return { onSubmit }
  }
})
</script>

FormGroup.vue:

<template lang="pug">
form(@submit.prevent='onFormSubmit', novalidate, autocomplete='on')
  slot Content needed in FormGroup
</template>

<script lang="ts">
import { defineComponent } from "vue"

export default defineComponent({
  name: "FormGroup",
  setup(_, { emit }) {
    const check = ref(true) // evaluated elsewhere - simplified for this example
    const onFormSubmit = (): void => {
      if (check.value) {
        alert("Form is valid - sending event to parent")
        emit("submit")
      } else {
        alert("Form is not valid.") // so don't emit the event to parent
      }
    }

    return { check, onFormSubmit }
  }
})
</script>

Any thoughts regarding why onSubmit() in the parent fires twice in Vue.js 3.0.0-rc.5?

2
  • Maybe a bug caused by <form> being the root element? I'd be curious if the same thing happened if FormGroup was rooted as a <div><form... instead Commented Aug 10, 2020 at 15:30
  • @nstuyvesant I just tried your code in Vue 2 with @vue/composition-api, and it also fires twice, so I don't think this is a bug in Vue 3. Daniel's answer should provide a solution. Commented Aug 11, 2020 at 2:01

1 Answer 1

4

Looks like it is intended in Vue 3

inheritAttrs

Type: boolean

Default: true

Details:

By default, parent scope attribute bindings that are not recognized as props will "fallthrough". This means that when we have a single-root component, these bindings will be applied to the root element of the child component as normal HTML attributes. When authoring a component that wraps a target element or another component, this may not always be the desired behavior. By setting inheritAttrs to false, this default behavior can be disabled. The attributes are available via the $attrs instance property and can be explicitly bound to a non-root element using v-bind.

Note: this option does not affect class and style bindings.

docs: https://v3.vuejs.org/api/options-misc.html#inheritattrs

Should be working (added inheritAttrs: false):

<template lang="pug">
form(@submit.prevent='onFormSubmit', novalidate, autocomplete='on')
  slot Content needed in FormGroup
</template>

<script lang="ts">
import { defineComponent } from "vue"

export default defineComponent({
  name: "FormGroup",
  inheritAttrs: false,
  setup(_, { emit }) {
    const check = ref(true) // evaluated elsewhere - simplified for this example
    const onFormSubmit = (): void => {
      if (check.value) {
        alert("Form is valid - sending event to parent")
        emit("submit")
      } else {
        alert("Form is not valid.") // so don't emit the event to parent
      }
    }

    return { check, onFormSubmit }
  }
})
</script>

const {
  defineComponent,
  createApp,
  ref,
} = Vue

const FormGroupFactory = (name, inheritAttrs) => defineComponent({
  name,
  inheritAttrs,
  setup(_, {
    emit
  }) {
    const onFormSubmit = () => {
      emit("evt", "@EVT")
      emit("submit", "@SUBMIT")
    }
    return {
      onFormSubmit
    }
  },
  template: document.getElementById("FormGroup").innerHTML
})


createApp({
    el: '#app',
    components: {
      FormGroupOne: FormGroupFactory('FormGroupOne', true),
      FormGroupTwo: FormGroupFactory('FormGroupTwo', false),
    },
    setup() {
      const log = ref('');
      const onSubmit = (e) => {
        log.value = log.value + "Parent event handler" + e + "\n"
      }
      return {
        log,
        onSubmit
      }

    }
  })
  .mount('#app')
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>

<div id="app">
  <div class="container">
    <form-group-one @submit="onSubmit"><button class="btn btn-primary" type="submit">Submit</button> inheritAttrs: true</form-group-one>
    <form-group-two @submit="onSubmit"><button class="btn btn-primary" type="submit">Submit</button> inheritAttrs: false</form-group-two>
  </div>
  <pre>{{log}}</pre>
</div>

<template id="FormGroup">
  <form @submit.prevent="onFormSubmit" novalidate="novalidate" autocomplete="on">
    <slot>Content needed in FormGroup</slot>
  </form>
</template>

Sign up to request clarification or add additional context in comments.

5 Comments

But inheritAttrs already exists in Vue 2 (as of 2.4.0), and its docs are identical in Vue 3. I think inheritAttrs is just a workaround.
I didn't check on vue 2, so I can't speak to the behaviour in vue 2, but as you can see in the example, setting it to false does reduce to one call
so is it that inheritAttrs: true (which is the default) isn't working on vue2.6? AFAIK seems like vue 3 is behaving correctly
As it turns out, the behavior is actually the same in Vue 2 (plain) and Vue 2 w/@vue/composition-api, so I don't think it's a bug anymore, and the solution is indeed as you stated (to use inheritAttrs: false).
This works well. Odd that I did not encounter the same issue with Vue 2.6.11 and Composition API but your guidance was spot on. Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.