Home > Net >  Vue.js 3: emit an event is causing an error
Vue.js 3: emit an event is causing an error

Time:01-03

I am trying to emit an event from a child component to its parent. I have read the guide at vuejs.org and thought that I had got the syntax correct. My child component (AxisLabel.vue) has

<script setup>
  ....
  const emit = defineEmits(['removeLetter', 'submitLetter'])
  const props = defineProps({
    stat: Object,
    index: Number,
    segments: Number
  })
  const point = computed(() =>
  {
    try {
      return valueToPoint(props.stat.value - 10, props.index, props.segments, true)
    }
    catch {
      return valueToPoint(0, 0, 9, true)
    }
  })
  function click() {
    if (props.stat.chosen) {
        props.stat.chosen = false;
        emit('removeLetter',props.stat.label)
    }
    else {
        props.stat.chosen = true;
        emit('submitLetter',props.stat.label)
    }
  }
  ....
</script>

<template>
    <text @click="click" :class=changeColour() 
        :x="point.x" :y="point.y">{{stat.label}}
    </text>
</template>

and in the parent (app.vue) I have:

<script setup>
import AxisLabel from './components/AxisLabel.vue'
const tryingWord = ref('word')

function addToWord(letter) {
  tryingWord.value = tryingWord.value   letter
}
function removeFromWord(letter) {
  // ....todo....
 
}
</script>

<template>

  <svg width="300" height="300">
    <PolyGraph :stats="stats"></PolyGraph>
  </svg>
  <text  rows="1" cols="9">
        {{tryingWord}}
  </text>

  <AxisLabel @submit-letter = "addToWord"/>
  <AxisLabel @remove-letter = "removeFromWord"/>
 
</template>

When I run this I get

[Vue warn]: Unhandled error during execution of render function at <AxisLabel onSubmitLetter=fn > at

followed by an 'undefined' error (see below). This happens as soon as the app is started, i.e. without the click event being fired.

If I remove the event listener definitions from app.vue, the error doesn't occur.

[later] I have now seen that, without the event listener defined, the <text> element of AxisLabel is called exacly 9 times (as it should be) - it's called from another component:

<axis-label
  v-for="(stat, index) in stats"
  :stat="stat"
  :index="index"
  :segments="stats.length-1">

and 'stats' has 9 members. But when I add back the event listeners, this element attempts to get created a tenth time, with invalid parameters, causing the crash - the Chrome debugger indicates that 'props' is undefined in here :

<text @click="click" :class=changeColour() 
    :x="point.x" :y="point.y">{{stat.label}}
</text>

'props' is used to calculate the position of the text, but adding the try/catch where it is used hasn't helped.

I need to see a working example of emitting an event in vue - vuejs.org doesn't seem to provide one.

CodePudding user response:

There is an error inside your event handler function. I'm not sure, if this is the problem, but it might be.

const tryingWord = ref('word')

function addToWord(letter) {
  // when working with refs, you need to use the .value property
  tryingWord.value = tryingWord.value   letter
}

Also, you should never mutate data, that is passed from the props as you do here:

function click() {
  if (props.stat.chosen) {
    // this mutates the props
    props.stat.chosen = false;
    emit('removeLetter',props.stat.label)
  }
  else {
    // this mutates the props
    props.stat.chosen = true;
    emit('submitLetter',props.stat.label)
  }
}

you will find an explanation about the one way data flow in the vue documentation.

Update

As mentioned in the comments, the AxisLabel component does not receive any props which will lead to errors when accessing the props inside the <script setup>.

// the following line will throw an error
// because props.stat === undefined
if (props.stat.chosen) {
  // ...
}

You could either define a default property that is used when no property is passed or check the prop for undefined before using it.

CodePudding user response:

The problem was here:

<template>
  <svg width="300" height="300">
    <PolyGraph :stats="stats"></PolyGraph>
  </svg>
  <text  rows="1" cols="9">
        {{tryingWord}}
  </text>

  <AxisLabel @submit-letter = "addToWord"/>
  <AxisLabel @remove-letter = "removeFromWord"/>
</template>

In fact there are 2 mistakes: Firstly, there are 2 calls to Axislabel and there should be just one:

<AxisLabel @submit-letter = "addToWord" @remove-letter = "removeFromWord"/>

Secondly, since Axislabel is a child of PolyGraph, I needed to propogate the events up through there to reach App.vue, i.e. these event handlers should be defined in PolyGraph.vue, not in App.vue

  • Related