How to use $emit with parameters in Vue

Using $emit with parameters in Vue enables powerful parent-child component communication by passing data, event details, and complex payloads from child components to their parents. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented $emit with parameters in thousands of Vue components for form submissions, user interactions, data updates, and complex event handling across enterprise component libraries. From my expertise, the most effective approach is to use descriptive event names with structured parameter objects. This method provides clear component APIs, type-safe event handling, and maintainable parent-child communication while following Vue’s unidirectional data flow principles.

Use $emit with descriptive event names and structured parameters to pass data from child to parent components.

<!-- Child Component: CustomButton.vue -->
<template>
  <button
    :class="buttonClasses"
    :disabled="disabled || loading"
    @click="handleClick"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
  >
    <span v-if="loading" class="spinner"></span>
    <slot></slot>
  </button>
</template>

<script>
export default {
  name: 'CustomButton',
  props: {
    variant: {
      type: String,
      default: 'primary'
    },
    size: {
      type: String,
      default: 'medium'
    },
    disabled: {
      type: Boolean,
      default: false
    },
    loading: {
      type: Boolean,
      default: false
    },
    data: {
      type: Object,
      default: () => ({})
    }
  },

  computed: {
    buttonClasses() {
      return [
        'custom-button',
        `custom-button--${this.variant}`,
        `custom-button--${this.size}`,
        {
          'custom-button--disabled': this.disabled,
          'custom-button--loading': this.loading
        }
      ]
    }
  },

  methods: {
    // 1. Simple event with single parameter
    handleClick(event) {
      if (this.disabled || this.loading) return

      // Emit with single parameter
      this.$emit('click', event)

      // Emit with multiple parameters
      this.$emit('button-clicked', event, this.variant, this.size)

      // Emit with structured object parameter
      this.$emit('action', {
        type: 'click',
        variant: this.variant,
        size: this.size,
        timestamp: new Date().toISOString(),
        element: event.target,
        data: this.data
      })
    },

    // 2. Mouse events with position data
    handleMouseEnter(event) {
      this.$emit('hover-start', {
        type: 'mouseenter',
        position: {
          x: event.clientX,
          y: event.clientY
        },
        target: event.target,
        variant: this.variant
      })
    },

    handleMouseLeave(event) {
      this.$emit('hover-end', {
        type: 'mouseleave',
        position: {
          x: event.clientX,
          y: event.clientY
        },
        duration: this.calculateHoverDuration(),
        target: event.target
      })
    },

    calculateHoverDuration() {
      // Implementation for calculating hover duration
      return Date.now() - (this.hoverStartTime || Date.now())
    }
  }
}
</script>
<!-- Child Component: UserForm.vue -->
<template>
  <form @submit.prevent="handleSubmit" class="user-form">
    <div class="form-group">
      <label>Name:</label>
      <input
        v-model="form.name"
        type="text"
        @input="handleInputChange('name', $event)"
        @blur="handleFieldBlur('name')"
      >
    </div>

    <div class="form-group">
      <label>Email:</label>
      <input
        v-model="form.email"
        type="email"
        @input="handleInputChange('email', $event)"
        @blur="handleFieldBlur('email')"
      >
    </div>

    <div class="form-group">
      <label>Age:</label>
      <input
        v-model.number="form.age"
        type="number"
        @input="handleInputChange('age', $event)"
      >
    </div>

    <div class="form-actions">
      <button type="submit" :disabled="!isFormValid">Submit</button>
      <button type="button" @click="handleReset">Reset</button>
      <button type="button" @click="handleCancel">Cancel</button>
    </div>
  </form>
</template>

<script>
export default {
  name: 'UserForm',
  props: {
    initialData: {
      type: Object,
      default: () => ({})
    }
  },

  data() {
    return {
      form: {
        name: '',
        email: '',
        age: null,
        ...this.initialData
      },
      errors: {},
      touchedFields: new Set()
    }
  },

  computed: {
    isFormValid() {
      return Object.keys(this.errors).length === 0 &&
             this.form.name.trim() &&
             this.form.email.trim()
    }
  },

  methods: {
    // 3. Form field change events with validation
    handleInputChange(fieldName, event) {
      const value = event.target.value

      // Update form data
      this.form[fieldName] = fieldName === 'age' ? parseInt(value) || null : value

      // Validate field
      this.validateField(fieldName, value)

      // Emit field change event with multiple parameters
      this.$emit('field-change', fieldName, value, {
        isValid: !this.errors[fieldName],
        form: { ...this.form },
        event
      })

      // Emit specific field events
      this.$emit(`${fieldName}-changed`, {
        value,
        isValid: !this.errors[fieldName],
        errors: this.errors[fieldName] || null
      })

      // Emit form state change
      this.$emit('form-update', {
        field: fieldName,
        value,
        form: { ...this.form },
        errors: { ...this.errors },
        isValid: this.isFormValid,
        changedFields: Array.from(this.touchedFields)
      })
    },

    // 4. Field blur events with validation
    handleFieldBlur(fieldName) {
      this.touchedFields.add(fieldName)

      this.$emit('field-blur', {
        field: fieldName,
        value: this.form[fieldName],
        isTouched: true,
        errors: this.errors[fieldName] || null
      })
    },

    // 5. Form submission with complete data
    handleSubmit() {
      // Final validation
      this.validateAllFields()

      if (this.isFormValid) {
        this.$emit('submit', {
          formData: { ...this.form },
          isValid: true,
          timestamp: new Date().toISOString(),
          touchedFields: Array.from(this.touchedFields)
        })

        // Also emit success event
        this.$emit('form-success', this.form)
      } else {
        this.$emit('submit', {
          formData: { ...this.form },
          isValid: false,
          errors: { ...this.errors },
          timestamp: new Date().toISOString()
        })

        // Emit validation error event
        this.$emit('validation-error', {
          errors: this.errors,
          form: this.form
        })
      }
    },

    // 6. Form reset with previous state
    handleReset() {
      const previousData = { ...this.form }

      this.form = {
        name: '',
        email: '',
        age: null,
        ...this.initialData
      }
      this.errors = {}
      this.touchedFields.clear()

      this.$emit('reset', {
        previousData,
        newData: { ...this.form },
        timestamp: new Date().toISOString()
      })
    },

    // 7. Form cancellation
    handleCancel() {
      const hasChanges = this.touchedFields.size > 0

      this.$emit('cancel', {
        hasUnsavedChanges: hasChanges,
        form: { ...this.form },
        touchedFields: Array.from(this.touchedFields)
      })
    },

    // Validation helper methods
    validateField(fieldName, value) {
      delete this.errors[fieldName]

      switch (fieldName) {
        case 'name':
          if (!value.trim()) {
            this.$set(this.errors, fieldName, 'Name is required')
          }
          break
        case 'email':
          if (!value.trim()) {
            this.$set(this.errors, fieldName, 'Email is required')
          } else if (!this.isValidEmail(value)) {
            this.$set(this.errors, fieldName, 'Invalid email format')
          }
          break
        case 'age':
          if (value !== null && (value < 0 || value > 150)) {
            this.$set(this.errors, fieldName, 'Age must be between 0 and 150')
          }
          break
      }
    },

    validateAllFields() {
      Object.keys(this.form).forEach(field => {
        this.validateField(field, this.form[field])
      })
    },

    isValidEmail(email) {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
    }
  }
}
</script>
<!-- Parent Component using the child components -->
<template>
  <div class="parent-component">
    <h2>Component Communication Examples</h2>

    <!-- 1. Button with event handling -->
    <div class="section">
      <h3>Custom Button Events</h3>
      <CustomButton
        variant="primary"
        size="large"
        :data="{ userId: 123, action: 'save' }"
        @click="handleButtonClick"
        @button-clicked="handleButtonClicked"
        @action="handleButtonAction"
        @hover-start="handleHoverStart"
        @hover-end="handleHoverEnd"
      >
        Save Changes
      </CustomButton>

      <div class="event-log">
        <h4>Button Events:</h4>
        <pre>{{ JSON.stringify(buttonEvents, null, 2) }}</pre>
      </div>
    </div>

    <!-- 2. Form with comprehensive event handling -->
    <div class="section">
      <h3>User Form Events</h3>
      <UserForm
        :initial-data="{ name: 'John Doe' }"
        @field-change="handleFieldChange"
        @name-changed="handleNameChanged"
        @email-changed="handleEmailChanged"
        @field-blur="handleFieldBlur"
        @form-update="handleFormUpdate"
        @submit="handleFormSubmit"
        @form-success="handleFormSuccess"
        @validation-error="handleValidationError"
        @reset="handleFormReset"
        @cancel="handleFormCancel"
      />

      <div class="event-log">
        <h4>Form Events:</h4>
        <pre>{{ JSON.stringify(formEvents.slice(-5), null, 2) }}</pre>
      </div>
    </div>

    <!-- 3. Dynamic component with parameter passing -->
    <div class="section">
      <h3>Dynamic Events</h3>
      <button @click="triggerCustomEvent">Trigger Custom Event</button>
    </div>
  </div>
</template>

<script>
import CustomButton from './CustomButton.vue'
import UserForm from './UserForm.vue'

export default {
  name: 'ParentComponent',
  components: {
    CustomButton,
    UserForm
  },

  data() {
    return {
      buttonEvents: [],
      formEvents: []
    }
  },

  methods: {
    // Button event handlers
    handleButtonClick(event) {
      this.buttonEvents.push({
        type: 'click',
        timestamp: new Date().toISOString(),
        event: 'Simple click event'
      })
    },

    handleButtonClicked(event, variant, size) {
      this.buttonEvents.push({
        type: 'button-clicked',
        timestamp: new Date().toISOString(),
        data: { variant, size }
      })
    },

    handleButtonAction(actionData) {
      this.buttonEvents.push({
        type: 'action',
        timestamp: new Date().toISOString(),
        data: actionData
      })
    },

    handleHoverStart(hoverData) {
      console.log('Hover started:', hoverData)
    },

    handleHoverEnd(hoverData) {
      console.log('Hover ended:', hoverData)
    },

    // Form event handlers
    handleFieldChange(fieldName, value, metadata) {
      this.formEvents.push({
        type: 'field-change',
        field: fieldName,
        value,
        metadata
      })
    },

    handleNameChanged(data) {
      console.log('Name changed:', data)
    },

    handleEmailChanged(data) {
      console.log('Email changed:', data)
    },

    handleFieldBlur(data) {
      console.log('Field blur:', data)
    },

    handleFormUpdate(updateData) {
      this.formEvents.push({
        type: 'form-update',
        data: updateData
      })
    },

    handleFormSubmit(submitData) {
      this.formEvents.push({
        type: 'submit',
        data: submitData
      })

      if (submitData.isValid) {
        // Process valid form submission
        console.log('Processing form:', submitData.formData)
      } else {
        // Handle validation errors
        console.log('Form has errors:', submitData.errors)
      }
    },

    handleFormSuccess(formData) {
      console.log('Form submitted successfully:', formData)
      // Show success message, redirect, etc.
    },

    handleValidationError(errorData) {
      console.log('Validation errors:', errorData)
      // Show error message
    },

    handleFormReset(resetData) {
      console.log('Form reset:', resetData)
    },

    handleFormCancel(cancelData) {
      console.log('Form cancelled:', cancelData)
      if (cancelData.hasUnsavedChanges) {
        // Show confirmation dialog
        const confirmed = confirm('You have unsaved changes. Are you sure you want to cancel?')
        if (confirmed) {
          // Handle cancellation
        }
      }
    },

    // 8. Dynamic event emission
    triggerCustomEvent() {
      // Simulate emitting event with complex parameters
      this.$emit('custom-action', {
        action: 'complex-operation',
        data: {
          items: [1, 2, 3],
          metadata: {
            timestamp: new Date().toISOString(),
            user: 'current-user'
          }
        },
        callback: (result) => {
          console.log('Operation completed:', result)
        }
      })
    }
  }
}
</script>

Use $emit with multiple parameters by passing additional arguments after the event name. Structure complex data as objects for better maintainability and type safety. Use descriptive event names that clearly indicate the action and context. Pass both the raw event data and processed metadata when helpful. Consider emitting multiple events for different use cases (general and specific). Always document the expected parameters in component documentation. Use Vue DevTools to debug event emissions and verify parameter passing during development.

Best Practice Note:

This is the same approach we use in CoreUI Vue components for comprehensive parent-child communication and event handling. Use structured parameter objects for complex data, emit multiple events for different granularities, include timestamps and metadata for debugging, validate emitted parameters in development, and document event contracts clearly in component APIs.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author