Fix: v-on With No Argument Expects an Object Value in Vue.js

Error message:
v-on with no argument expects an object value.
Directives 2025-01-25

What Causes This Warning?

This warning occurs when you use v-on without specifying an event name (using the object syntax) but pass a non-object value.

The Problem

<!-- ❌ v-on with no argument but not an object -->
<template>
  <button v-on="handleClick">Click</button>
  <button v-on="'click'">Click</button>
  <button v-on="123">Click</button>
</template>

The Fix

Use Object Syntax

<template>
  <!-- ✅ Object with event: handler pairs -->
  <button v-on="{ click: handleClick }">Click</button>

  <!-- ✅ Multiple events -->
  <button v-on="{ click: handleClick, mouseenter: handleHover }">
    Hover or Click
  </button>
</template>

<script setup>
function handleClick() {
  console.log('Clicked!')
}

function handleHover() {
  console.log('Hovered!')
}
</script>

Use Specific Event Binding

<template>
  <!-- ✅ Standard event binding -->
  <button @click="handleClick">Click</button>
  <button v-on:click="handleClick">Click</button>
</template>

Common Scenarios

Dynamic Event Binding

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

const events = computed(() => ({
  click: handleClick,
  mouseenter: handleMouseEnter,
  mouseleave: handleMouseLeave
}))
</script>

<template>
  <!-- ✅ Bind multiple events dynamically -->
  <div v-on="events">
    Interactive element
  </div>
</template>

Passing Events to Child Components

<script setup>
// Parent component
const buttonEvents = {
  click: () => console.log('clicked'),
  focus: () => console.log('focused'),
  blur: () => console.log('blurred')
}
</script>

<template>
  <!-- ✅ Pass event object to child -->
  <CustomButton v-on="buttonEvents" />
</template>

Forwarding All Listeners

<!-- Wrapper component -->
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()

// In Vue 3, listeners are part of attrs
const listeners = computed(() => {
  const result = {}
  for (const key in attrs) {
    if (key.startsWith('on') && typeof attrs[key] === 'function') {
      result[key] = attrs[key]
    }
  }
  return result
})
</script>

<template>
  <div class="wrapper">
    <!-- Forward all event listeners -->
    <InnerComponent v-bind="listeners" />
  </div>
</template>

Conditional Events

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

const isEnabled = ref(true)

const conditionalEvents = computed(() => {
  if (!isEnabled.value) return {}

  return {
    click: handleClick,
    keydown: handleKeydown
  }
})
</script>

<template>
  <button v-on="conditionalEvents">
    {{ isEnabled ? 'Active' : 'Disabled' }}
  </button>
</template>

Combining Static and Dynamic Events

<template>
  <!-- ✅ Mix static and dynamic events -->
  <button
    @click="handleClick"
    v-on="additionalEvents"
  >
    Button
  </button>
</template>

<script setup>
const additionalEvents = {
  mouseenter: () => console.log('enter'),
  mouseleave: () => console.log('leave')
}
</script>

With Event Modifiers

<template>
  <!-- When using object syntax, add modifiers differently -->
  <form v-on="formEvents">
    <button type="submit">Submit</button>
  </form>
</template>

<script setup>
const formEvents = {
  submit: (e) => {
    e.preventDefault() // Manual modifier
    handleSubmit()
  }
}
</script>

Options API

export default {
  computed: {
    eventHandlers() {
      return {
        click: this.handleClick,
        mouseenter: this.handleMouseEnter
      }
    }
  },
  methods: {
    handleClick() { /* ... */ },
    handleMouseEnter() { /* ... */ }
  }
}
<template>
  <div v-on="eventHandlers">Content</div>
</template>

Quick Checklist

  • v-on without argument requires an object: v-on="{ event: handler }"
  • For single events, use @event="handler" or v-on:event="handler"
  • Object keys are event names, values are handler functions
  • Use computed for dynamic event objects
  • Modifiers must be handled manually in object syntax