Home > front end >  How to pass @blur into child component Vuejs
How to pass @blur into child component Vuejs

Time:10-08

I created a custom Input component.

I needed pass @blur from vee-validate form to my custom input component.

It works great in normal html input tag. I no idea how could we pass the @blur into custom Input component.

Example 1 work correctly, it triggered the validation after blur the input.

 <template>
  <form @submit="onSubmit">
    <input @blur="emailBlur" v-model="email" type="text" autocomplete="off" name="email" placeholder="email">
    <button type="submit" :disabled="isSubmitting">Submit</button>
  </form>
 </template>

Example 2 with My custom Input Component:

// src/components/Input.vue
<template>
  <div class="mt-2">
    <label :for="name" class="h5">Name</label>
    <input
      :type="type"
      :value="modelValue"
      @change="$emit('update:modelValue', $event.target.value)"
      :id="name"
      :placeholder="placeholder"
      />
  </div>
</template>

<script>
export default {
  name: 'Input',
  props: ["modelValue", 'name', 'type', 'placeholder'],
  setup(props) {
    console.log('props :>> ', props); // not receive the @blur 
  }
}
</script>

Parent Component (App.vue):

<template>
  <form>
   <Input @blur="emailBlur" v-model="email" type="text" name="email" placeholder="Custom input email" />
    <button type="submit" :disabled="isSubmitting">Submit</button>
  </form>

</template>

export default {
  name: 'App',
  components: {
    Input
  },
  setup() {
     // the vee validation values v-model to template
  }
}

CodePudding user response:

What you are creating is usually called "transparent wrapper" component. What you want from this wrapper is to behave in almost every way as normal input component so the users of the component can work with it as it was normal input (but it is not)

In your case, you want to attach @blur event listener to your wrapper. But the problem is that blur is native browser event i.e. not a Vue event.

When you place event listener on a component for an event not specified in emits option (or v-bind an attribute that is not specified in component's props), Vue will treat it as Non-Prop Attribute. This means it take all such event listeners and non-prop attributes and place it on the root node of the component (div in your case)

But luckily there is a way to tell Vue "Hey, don't place those automatically on root, I know where to put it"

  1. Use inheritAttrs: false option on your component
  2. Put all non-prop attributes (including the event listeners) on the element you want - input in this case - using v-bind="$attrs"

Now you can even remove some props - for example placeholder (if you want), because if you use it directly on your component, Vue place it on input, which is what you want...

Also handling @change event is not optimal - you component allows to specify a type and different input types has different events. Nice trick around it is not to pass value and bind event explicitly, but instead use v-model with computed (see example below)

const app = Vue.createApp({
  data() {
    return {
      text: ""
    }
  },
  methods: {
    onBlur() {
      console.log("Blur!")
    }
  }
  
})

app.component('custom-input', {
  inheritAttrs: false,
  props: ["modelValue", 'name', 'type'],
  computed: {
    model: {
      get() { return this.modelValue },
      set(newValue) { this.$emit('update:modelValue', newValue) } 
    }
  },
  template: `
  <div class="mt-2">
    <label :for="name" class="h5">{{ name }}:</label>
    <input
      :type="type"
      v-model="model"
      :id="name"
      v-bind="$attrs"
    />
  </div>
  `
})

app.mount("#app")
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id='app'>
  <custom-input type="text" name="email" v-model="text" placeholder="Type something..." @blur="onBlur"></custom-input>
  <pre>{{ text }}</pre>
</div>

CodePudding user response:

Sending the handler as props from the parent is not a good practice instead need to trigger the handler(present in the parent) from the child component. By doing so you will have an advantage where you can bind different blur handlers based on your requirement inside different parent components

To do so you can follow the below approach

Custom Input Component

// src/components/Input.vue
<template>
  <div class="mt-2">
    <label :for="name" class="h5">Name</label>
    <input
      :type="type"
      :value="modelValue"
      @change="$emit('update:modelValue', $event.target.value)"
      :id="name"
      :placeholder="placeholder"
      @blur="$emit('blur')" //Change added
      />
  </div>
</template>

<script>
export default {
  name: 'Input',
  props: ["modelValue", 'name', 'type', 'placeholder'],
  emits: ['blur', 'update:modelValue'], // change added
}
</script>

Note:

for all v-models without arguments, make sure to change props and events name to modelValue and update:modelValue respectively

For Example:

Parent.vue

<ChildComponent v-model="pageTitle" />

and in Child.vue it should be like

export default {
  props: {
    modelValue: String // previously was `value: String`
  },
  emits: ['update:modelValue'],
  methods: {
    changePageTitle(title) {
      this.$emit('update:modelValue', title) // previously was `this.$emit('input', title)`
    }
  }
}
  • Related