skip to Main Content

I’m new to Vue and Javascript in general and am currently trying to figure out how to pass data from a child component to a parent component. I’m trying to write a simple calculator, but I’ve run into the following problem. My calculator is split into several components, namely Button, ButtonPanel, Display and Calculator (required). The ButtonPanel consists of number buttons and arithmetic buttons. Each button has props btnValue. The expression variable is declared in the Calculator component, and my task, when you click on the corresponding button, is to add its value to the one that is displayed. The problem is that when the button is clicked, instead of its btnValue, btnValue[object PointerEvent] is added to expression. What is the problem and is there a way around it?

This is my sources:

Calculator.vue:

<template>
  <div class="calculator-container">
    <Display :expression="expression || '0'"></Display>
    <ButtonPanel @update-expression="updateExpression"></ButtonPanel>
  </div>
</template>

<script>
import Display from "@/components/Display";
import ButtonPanel from "@/components/ButtonPanel";
import {ref} from "vue";

export default {
  name: 'CalculatorComponent',
  components: {ButtonPanel, Display},
  props: {},
  setup() {
    const expression = ref('');
    const updateExpression = (value) => {
      expression.value += value;
    };
    return {
      expression,
      updateExpression
    }
  }
}
</script>

//Styles, etc

Button.vue

<template>
  <div class="button" @click="handleClick(this.btnValue)">
    <h2 class="text-white m-0">{{btnValue}}</h2>
  </div>
</template>

<script>
export default {
  // eslint-disable-next-line vue/multi-word-component-names
  name: 'Button',
  props: {
    btnValue: String
  },
  methods:{
    handleClick(value){
      console.log(value)
      this.$emit('click',value);
    }
  }
}
</script>

ButtonPanel.vue:

<template>
  <div class="button-panel">
    <Button @click="addToExpression" btn-value="1"></Button>
    <Button @click="addToExpression" btn-value="2"></Button>
    //other-buttons
  </div>
</template>

<script>
import Button from "@/components/Button";

export default {
  name: 'ButtonPanel',
  components: {Button},
  methods:{
    addToExpression(value){
      console.log(value)
      this.$emit('update-expression',value)
    }
  }
}
</script>

2

Answers


  1. Chosen as BEST ANSWER

    Ok, changing the name of event to btn-click, which Button.vue produces, worked for me. If I understood correctly, @click is kind of reserved name for event and it produces PointerEvent object, so you have to give it a custom name, and then subscribe to this event in parent component


  2. The issue comes from not declaring the events your component emits:

    export default {
      name: 'CalculatorComponent',
      emits: ['click'], // <-----  do this
      ...
    

    As it says in the documentation:

    the emits option affects which event listeners are considered component event listeners, rather than native DOM event listeners

    So when you are not declaring the custom click event, the @click listener on the Button components will be triggered by the event emitted from the Button component, but also by the native click event bubbling upwards from the div.button element. The bubbling event will contain the native click event object.

    Have a look at the snippet, I hope it demonstrates the difference between declaring the event and not doing so:

    const { createApp, ref } = Vue;
    
    const CompWithoutEmit = {
      template: `<button @click="doEmit">{{title}}</button>`,
      data(){return {title: 'emit undeclared event (@click will receive custom and native event)'}},
      methods: {
        doEmit(){
          console.log('button was clicked')
          this.$emit('click')
        }
      }
    }
    
    const CompWithEmit = {
      ...CompWithoutEmit,
      emits: ['click'],
      data(){return {title: 'emit declared event (@click will receive only custom click event)'}},
    }
    
    const App = { 
      components: {
        'component-without-emit': CompWithoutEmit,
        'component-with-emit': CompWithEmit,
        },
      methods: {
        handleClick(e){
          const isClickEvent = e?.type === 'click'
          console.log(isClickEvent ? 'got native click event' : 'got custom click event')
        } 
      }
    }
    const app = createApp(App)
    app.mount('#app')
    console.clear()
    <div id="app">
      <component-without-emit @click="handleClick"></component-without-emit>
      <component-with-emit @click="handleClick"></component-with-emit>
    </div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search