search
Vue star Featured

How to Fix: Vue 3 emit is not defined error

Learn how to fix the 'emit is not defined' error in Vue 3 applications. This comprehensive guide covers Composition API, defineEmits, and best practices.

person By Gautam Sharma
calendar_today January 2, 2026
schedule 12 min read
Vue 3 emit Composition API Error Frontend Development Events

The ‘Vue 3 emit is not defined’ error is a common Vue 3 issue that occurs when trying to emit events in the Composition API without properly defining or accessing the emit function. This error typically happens when using emit without calling defineEmits or when the emit function isn’t properly destructured from the component context.

This comprehensive guide explains what causes this error, why it happens, and provides multiple solutions to fix it in your Vue 3 projects with clean code examples and directory structure.


What is the Vue 3 emit Error?

The “emit is not defined” error occurs when:

  • Using emit without defining it with defineEmits()
  • Trying to access emit in the wrong context
  • Not destructuring emit from the setup function parameters
  • Using emit in <script setup> without proper declaration
  • Attempting to emit events without proper event definition

Common Error Messages:

  • emit is not defined
  • Cannot access 'emit' before initialization
  • ReferenceError: emit is not defined
  • emit is not a function
  • TypeError: emit is not a function

Understanding the Problem

In Vue 3’s Composition API, event emission works differently than in Vue 2. The emit function must be explicitly defined using defineEmits() in the component setup. This change provides better type safety and clearer event contracts between parent and child components.

Typical Vue 3 Project Structure:

my-vue3-app/
├── package.json
├── vite.config.js
├── src/
│   ├── main.js
│   ├── App.vue
│   ├── components/
│   │   ├── ChildComponent.vue
│   │   └── ParentComponent.vue
│   ├── composables/
│   │   └── useEvents.js
│   ├── assets/
│   └── styles/
│       └── main.css
└── public/

Solution 1: Use defineEmits in Script Setup

The most common solution for <script setup> components.

❌ Without defineEmits:

<template>
  <div>
    <button @click="handleClick">Click me</button>
  </div>
</template>

<script setup>
// ❌ emit is not defined
const handleClick = () => {
  emit('button-clicked', 'Hello from child') // ❌ Error: emit is not defined
}
</script>

✅ With defineEmits:

ChildComponent.vue:

<template>
  <div>
    <button @click="handleClick">Click me</button>
  </div>
</template>

<script setup>
// ✅ Define emits using defineEmits
const emit = defineEmits(['button-clicked', 'data-updated'])

const handleClick = () => {
  // ✅ Now emit is available
  emit('button-clicked', 'Hello from child')
}

const updateData = (data) => {
  emit('data-updated', data)
}
</script>

Solution 2: Use Setup Function with Context

For components using the setup function syntax.

❌ Without Context:

<template>
  <div>
    <button @click="handleClick">Click me</button>
  </div>
</template>

<script>
export default {
  name: 'ChildComponent',
  setup() {
    // ❌ emit is not available in this context
    const handleClick = () => {
      emit('button-clicked') // ❌ Error: emit is not defined
    }
    
    return {
      handleClick
    }
  }
}
</script>

✅ With Context:

<template>
  <div>
    <button @click="handleClick">Click me</button>
  </div>
</template>

<script>
export default {
  name: 'ChildComponent',
  emits: ['button-clicked', 'data-updated'], // ✅ Define emitted events
  setup(props, { emit }) { // ✅ Destructure emit from context
    const handleClick = () => {
      emit('button-clicked', 'Hello from child') // ✅ emit is now available
    }
    
    const updateData = (data) => {
      emit('data-updated', data)
    }
    
    return {
      handleClick,
      updateData
    }
  }
}
</script>

Solution 3: Define Emits with Validation

Define emits with proper validation and types.

❌ Without Validation:

<script setup>
// ❌ No validation for emitted events
const emit = defineEmits(['update', 'delete'])
</script>

✅ With Validation:

<script setup>
// ✅ Define emits with validation
const emit = defineEmits({
  'update': (value) => {
    // ✅ Validate the emitted value
    if (typeof value !== 'string') {
      console.warn('Update event expects a string value')
      return false
    }
    return true
  },
  'delete': (id) => {
    // ✅ Validate the ID parameter
    if (typeof id !== 'number' || id <= 0) {
      console.warn('Delete event expects a positive number ID')
      return false
    }
    return true
  }
})
</script>

Solution 4: Use TypeScript with defineEmits

For better type safety with TypeScript.

❌ Without TypeScript:

<script setup>
const emit = defineEmits(['increment', 'decrement'])
</script>

✅ With TypeScript:

<script setup lang="ts">
// ✅ Define emits with TypeScript
interface Emits {
  (e: 'increment', value: number): void
  (e: 'decrement', value: number): void
  (e: 'reset'): void
}

const emit = defineEmits<Emits>()

const increment = (amount: number = 1) => {
  emit('increment', amount)
}

const decrement = (amount: number = 1) => {
  emit('decrement', amount)
}

const reset = () => {
  emit('reset')
}
</script>

Solution 5: Handle Async Emits Properly

For async operations that need to emit events.

AsyncComponent.vue:

<template>
  <div>
    <button @click="handleAsyncAction">Async Action</button>
    <div v-if="loading">Loading...</div>
    <div v-if="error" class="error">{{ error }}</div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// ✅ Define async-related emits
const emit = defineEmits(['async-success', 'async-error'])

const loading = ref(false)
const error = ref(null)

const handleAsyncAction = async () => {
  loading.value = true
  error.value = null
  
  try {
    // ✅ Simulate async operation
    const result = await performAsyncOperation()
    
    // ✅ Emit success event
    emit('async-success', result)
  } catch (err) {
    error.value = err.message
    
    // ✅ Emit error event
    emit('async-error', err)
  } finally {
    loading.value = false
  }
}

const performAsyncOperation = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = Math.random() > 0.2 // 80% success rate
      if (success) {
        resolve({ data: 'Success data', timestamp: Date.now() })
      } else {
        reject(new Error('Async operation failed'))
      }
    }, 1000)
  })
}
</script>

<style scoped>
.error {
  color: red;
  margin-top: 10px;
}
</style>

Solution 6: Use Composables for Event Handling

Create reusable event handling logic with composables.

composables/useEvents.js:

import { getCurrentInstance } from 'vue'

export function useEventEmitter() {
  const instance = getCurrentInstance()
  
  if (!instance) {
    throw new Error('useEventEmitter must be used within a component')
  }
  
  const emit = instance.emit
  
  const emitWithValidation = (event, payload) => {
    // ✅ Add validation logic here
    if (event && emit) {
      emit(event, payload)
    }
  }
  
  return {
    emit: emitWithValidation,
    emitAsync: async (event, payload) => {
      return new Promise((resolve) => {
        emit(event, payload)
        resolve()
      })
    }
  }
}

Component using composable:

<template>
  <div>
    <button @click="handleClick">Emit Event</button>
  </div>
</template>

<script setup>
import { useEventEmitter } from '@/composables/useEvents'

// ✅ Use the event emitter composable
const { emit } = useEventEmitter()

const handleClick = () => {
  emit('button-clicked', { message: 'Hello from composable' })
}
</script>

Working Code Examples

Complete Parent-Child Communication Example:

<!-- ParentComponent.vue -->
<template>
  <div class="parent">
    <h2>Parent Component</h2>
    <p>Child Message: {{ childMessage }}</p>
    <p>Counter: {{ counter }}</p>
    
    <ChildComponent 
      @greeting="handleGreeting"
      @counter-updated="handleCounterUpdate"
      @error="handleError"
    />
    
    <div class="actions">
      <button @click="resetCounter">Reset Counter</button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const childMessage = ref('')
const counter = ref(0)

const handleGreeting = (message) => {
  childMessage.value = message
}

const handleCounterUpdate = (newCount) => {
  counter.value = newCount
}

const handleError = (error) => {
  console.error('Child component error:', error)
}

const resetCounter = () => {
  counter.value = 0
  childMessage.value = ''
}
</script>

<style scoped>
.parent {
  padding: 20px;
  border: 2px solid #42b983;
  border-radius: 8px;
  margin: 20px;
}

.actions {
  margin-top: 20px;
}

.actions button {
  padding: 8px 16px;
  background-color: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>
<!-- ChildComponent.vue -->
<template>
  <div class="child">
    <h3>Child Component</h3>
    <p>Current count: {{ count }}</p>
    
    <div class="buttons">
      <button @click="increment">Increment</button>
      <button @click="decrement">Decrement</button>
      <button @click="sendGreeting">Send Greeting</button>
      <button @click="triggerError">Trigger Error</button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// ✅ Define all emitted events
const emit = defineEmits({
  'greeting': (message) => {
    if (typeof message !== 'string') {
      console.warn('Greeting event expects a string')
      return false
    }
    return true
  },
  'counter-updated': (count) => {
    if (typeof count !== 'number') {
      console.warn('Counter-updated event expects a number')
      return false
    }
    return true
  },
  'error': (error) => {
    if (typeof error !== 'object') {
      console.warn('Error event expects an error object')
      return false
    }
    return true
  }
})

const count = ref(0)

const increment = () => {
  count.value++
  emit('counter-updated', count.value)
}

const decrement = () => {
  count.value--
  emit('counter-updated', count.value)
}

const sendGreeting = () => {
  emit('greeting', `Hello from child! Count is: ${count.value}`)
}

const triggerError = () => {
  emit('error', { message: 'Something went wrong', code: 500 })
}
</script>

<style scoped>
.child {
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 4px;
  background-color: #f9f9f9;
}

.buttons {
  margin-top: 15px;
}

.buttons button {
  margin-right: 10px;
  padding: 6px 12px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.buttons button:hover {
  background-color: #0056b3;
}
</style>

Best Practices for Event Handling

1. Always Define Emits Explicitly

// ✅ Always define emits explicitly
const emit = defineEmits(['event-name'])

2. Use Descriptive Event Names

// ✅ Use clear, descriptive event names
const emit = defineEmits(['user-updated', 'form-submitted', 'item-deleted'])

3. Validate Emitted Data

// ✅ Validate data before emitting
const emit = defineEmits({
  'data-updated': (data) => {
    return typeof data === 'object' && data !== null
  }
})

4. Handle Async Operations Properly

// ✅ Handle async operations with proper error handling
const handleAsync = async () => {
  try {
    const result = await asyncOperation()
    emit('success', result)
  } catch (error) {
    emit('error', error)
  }
}

Debugging Steps

Step 1: Check defineEmits Usage

# Verify defineEmits is used in script setup
grep -r "defineEmits" src/components/

Step 2: Verify Event Definitions

// Check if events are properly defined
// In your component: const emit = defineEmits(['event-name'])

Step 3: Use Vue DevTools

# Install Vue DevTools browser extension
# Inspect component events and emit functionality

Step 4: Add Console Logs

// Add debugging logs around emit calls
const handleClick = () => {
  console.log('About to emit event')
  emit('button-clicked', 'data')
  console.log('Event emitted successfully')
}

Common Mistakes to Avoid

1. Forgetting to Define Emits

// ❌ Don't use emit without defining it
const handleClick = () => {
  emit('event') // ❌ emit is not defined
}

2. Using emit Outside Setup

// ❌ Don't use emit outside of setup function
export default {
  methods: {
    handleClick() {
      emit('event') // ❌ emit is not available here
    }
  }
}

3. Incorrect Destructuring in Setup

// ❌ Wrong destructuring
setup(props, context) {
  // ❌ Forgot to destructure emit
  const handleClick = () => {
    emit('event') // ❌ emit is not defined
  }
}

// ✅ Correct destructuring
setup(props, { emit }) {
  const handleClick = () => {
    emit('event') // ✅ emit is available
  }
}

4. Not Using defineEmits in Script Setup

<!-- ❌ Missing defineEmits -->
<script setup>
const handleClick = () => {
  emit('event') // ❌ Error
}
</script>

Performance Considerations

1. Minimize Event Emissions

// ✅ Don't emit events too frequently
// Debounce or throttle frequent events if needed

2. Use Efficient Event Handling

// ✅ Use efficient event handling
const handleInput = (value) => {
  // Consider debouncing for input events
  emit('input', value)
}

Security Considerations

1. Validate Emitted Data

// ✅ Always validate data before emitting
const emit = defineEmits({
  'user-data': (data) => {
    // Validate sensitive data before emitting
    return isValidUserData(data)
  }
})

2. Sanitize Sensitive Information

// ✅ Don't emit sensitive information
const handleSensitiveData = (data) => {
  // Remove sensitive fields before emitting
  const safeData = sanitizeData(data)
  emit('data-ready', safeData)
}

Testing Event Emission

1. Unit Test Event Emission

import { mount } from '@vue/test-utils'
import ChildComponent from '@/components/ChildComponent.vue'

describe('ChildComponent', () => {
  it('should emit events properly', () => {
    const wrapper = mount(ChildComponent)
    
    // Trigger the event
    wrapper.find('button').trigger('click')
    
    // Assert that the event was emitted
    expect(wrapper.emitted()).toHaveProperty('button-clicked')
  })
  
  it('should emit events with correct payload', () => {
    const wrapper = mount(ChildComponent)
    
    wrapper.vm.handleClick()
    
    const emitted = wrapper.emitted('button-clicked')
    expect(emitted).toBeTruthy()
    expect(emitted[0]).toEqual(['Hello from child'])
  })
})

2. Test Event Validation

it('should validate emitted data', () => {
  const wrapper = mount(ChildComponent)
  
  // Test with invalid data
  expect(() => {
    wrapper.vm.emit('greeting', 123) // Invalid - should be string
  }).toThrow()
})

Alternative Solutions

1. Use Provide/Inject for Complex Communication

// For complex parent-child communication
provide('eventBus', {
  emit: (event, data) => {
    // Custom event handling logic
  }
})

2. Use Vuex/Pinia for State Management

// For complex state management needs
import { useStore } from 'vuex'

const handleClick = () => {
  store.dispatch('updateState', data)
}

Migration Checklist

  • Verify all components use defineEmits in <script setup>
  • Check that emits are properly defined in setup functions
  • Add event validation where appropriate
  • Test all event emissions work correctly
  • Update TypeScript interfaces for events
  • Run unit tests to verify event functionality
  • Update documentation for team members

Conclusion

The ‘Vue 3 emit is not defined’ error is a common issue that occurs when trying to emit events without properly defining the emit function. By following the solutions provided in this guide—whether through proper defineEmits usage, correct setup function syntax, or TypeScript integration—you can ensure your Vue 3 applications handle event communication correctly.

The key is to understand that Vue 3’s Composition API requires explicit definition of emits using defineEmits() in <script setup> components or proper destructuring from the setup context. With proper event handling, your Vue 3 applications will have clear, type-safe communication between parent and child components.

Remember to always define your emits explicitly, validate emitted data when necessary, and test your event handling thoroughly to ensure reliable component communication throughout your application.

Gautam Sharma

About Gautam Sharma

Full-stack developer and tech blogger sharing coding tutorials and best practices

Related Articles

Vue

Fix: defineProps is not defined in Vue.js Error

Learn how to fix the 'defineProps is not defined' error in Vue.js applications. This comprehensive guide covers Composition API, TypeScript, and best practices.

January 2, 2026
Vue

Fix: v-model not working in Vue 3 Error

Learn how to fix v-model issues in Vue 3 applications. This comprehensive guide covers Composition API, custom events, and best practices.

January 2, 2026
Vue

Fix: Cannot read property '$refs' of undefined in Vue.js

Learn how to fix the 'Cannot read property $refs of undefined' error in Vue.js applications. This comprehensive guide covers Vue instance access, lifecycle hooks, and best practices.

January 2, 2026