Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

After some research the following suggestion by Mr. Evan You was found: https://github.com/vuejs/vue/issues/7349#issuecomment-354937350

So without any hesitation I gave it a try:

Component template

<template>
  <div v-on='{ click: dataType === `section` ? toggleSectionElements : null }'>
    ... magic 
<template>

JS Logic

<script>
export default {
  name: `product-section`,
  props: [`section`, `sectionName`, `depth`],
  methods: {
    toggleSectionElements() {
      ... magic 
  computed: {
    dataType() {
      if (this.$props.section.sections || this.$props.depth === 0) {
        return `section`
      } else {
        return `element`
</script>

But for described case it results in error during rendering:

[Vue warn]: Invalid handler for event "click": got null

Can someone please suggest what has been done wrong? :thinking:

Update
The way Data Model looks like:

DataModel: {
  mainSectionA: {
    sections: {
      sectionA: {
        sections: {
          elementA: { values: { ... } },     
          elementB: { values: { ... } }
        values: { ... }
      sectionB: {
        elementA: { values: { ... } },
        elementB: { values: { ... } }
    values: { ... }
  mainSectionB: {
    sections: {
      elementA: { values: { ... } },
      elementB: { values: { ... } },  
      elementC: { values: { ... } },
      ... elements
    values: { ... }

Just change it to the below and it will work

v-on="condition ? { mouseover: handler } : {}"

or, if your handler is called mouseover

v-on="condition ? { mouseover } : {}"

Instead of polluting your template with ternary logic, you should actually perform the check inside the click handler instead. It not only makes your template more readable, but also makes maintaining the code easier since all logic has been abstracted and delegated to the event handler's callback instead.

Quick solution

Therefore the quick solution is to actually ensure that the toggleSectionElements() will only work when a correct dataType is present. This can be achieved by using a guard clause:

toggleSectionElements() {
  // Guard clause to prevent further code execution
  if (this.dataType() !== 'section')
    return;
  // Magic here

Even better, is that if separate handlers should be assigned to each dataType: you can then create a factory function for that purpose:

methods: {
  // This is just a factory function
  toggleElements() {
    switch (this.dataType()) {
      case 'section':
        return this.toggleSectionElements;
      case 'element':
        // Something else...
  toggleSectionElements() {
    // Magic for section element

Suggestion: using atomic components

Since it might be costly to bind click event handlers to elements that end up doing nothing, you can also break down your component to be more atomic. The collection element will be responsible of receiving an array of "section" or "element", and each "section"/"element" will have its own component, something like this:

  • You have a collection component, say <my-collection>, that holds all "section" and "element" components
  • "section" component will use the <my-section> component
  • "element" component will use the <my-element> component
  • This is when VueJS becomes really powerful: you can use dynamic component inside <my-collection> to determine which component to use depending on the dataType encountered.

    This is done by running a v-for through the collection, and then using v-bind:is="..." to determine whether a specific collection item should be using "section" or "element". I understand that this is probably going to go out of scope of your original question, but it's a worthwhile design to consider:

    const collectionComponent = Vue.component('my-collection', {
      template: '#my-collection-component',
      data: function() {
        return {
          collection: [{
            dataType: 'section',
            description: 'Hello I am section 1'
            dataType: 'element',
            description: 'Hello I am element 1'
            dataType: 'section',
            description: 'Hello I am section 2'
            dataType: 'element',
            description: 'Hello I am element 2'
      methods: {
        componentToUse(dataType) {
          return 'my-' + dataType;
    const sectionComponent = Vue.component('my-section', {
      template: '#my-section-component',
      props: ['itemData'],
      methods: {
        toggle() {
          console.log('Doing some magic.');
    const elementComponent = Vue.component('my-element', {
      template: '#my-element-component',
      props: ['itemData']
    new Vue({
      el: '#app'
    
    .box {
      border: 1px solid #999;
      cursor: pointer;
      margin: 10px;
      padding: 10px;
    .box:hover {
      background-color: #eee;
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <div id="app">
      <my-collection />
    <script type="text/x-template" id="my-collection-component">
        <component
          v-for="(item, i) in collection"
          v-bind:key="i"
          v-bind:is="componentToUse(item.dataType)"
          v-bind:itemData="item" />
    </script>
    <script type="text/x-template" id="my-section-component">
      <div @click="toggle" class="box">
        <h1>{{ itemData.dataType }}</h1>
        <p>{{ itemData.description }}</p>
        <p>Clicking on me will invoke a section-specific logic</p>
    </script>
    <script type="text/x-template" id="my-element-component">
      <div class="box">
        <h1>{{ itemData.dataType }}</h1>
        <p>{{ itemData.description }}</p>
        <p>Clicking on me will do nothing</p>
    </script>
    Thank you for your suggestion, Terry. I was thinking about your suggestion as initial approach. I haven't posted here but the this template is some sort of a recursive component. Where at the end it ends up rendering around 10 sections and 60 elements. – volna Feb 23, 2019 at 8:53 Sections should have the listener, while the elements don't, do you find it reasonable to actually check within the listener, rather then not adding it all? (actually curios) – volna Feb 23, 2019 at 8:54 @volna That's an appropriate concern. A better solution is to actually have atomic components: in this case, you will have two separate components: one to be consumed by "section" and another to be consumed by "element". The "section" component can have the click event bound to it, while the "element" don't have to. – Terry Feb 23, 2019 at 9:01

    in the not-equal case you pass null, but the value on click expects a function. you can try an emptry function:

    click: dataType === `section` ? toggleSectionElements : ()=>{}
    

    In Vue 3 you can pass null to the listener. Combining it with optional chaining you can do this:

    @click="handler?.() || null"
    

    Same for old browsers:

    @click="handler ? handler() : null"
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.