Vue.js 2.x Passing function to component

I need to render the same component several times with different information and when someone selects something it should load a function in the main Vue instance.

Until now I wasn't able to pass a dynamic function as a property like @change="onchange":

[Vue warn]: Invalid handler for event "change": got undefined

found in

---> at resources\assets\js\components\GeographyField.vue

The component:

<template>
    <fieldset class="form-group col-md">
        <label :for="name" class="control-label" v-html="label"></label>
        <select class="form-control" :name="name" :id="name" :model="_default" :disabled="disabled" @change="onchange">
            <option :value="0" v-html="placeholder"></option>
            <option v-for="object in collection" :value="object.id">{{ object.name }}</option>
        </select>
    </fieldset>
</template>

<script>
    module.exports = {
        props: {
            label: {
                type: String,
                default: 'Options'
            },
            _default: {
                type: Number,
                default: 0
            },
            disabled: {
                type: Boolean,
                default: false
            },
            placeholder: {
                type: String,
                default: 'Choose an option'
            },
            name: {
                type: String,
                required: true,
            },
            collection: {
                type: Array,
                required: true
            },
            onchange: {

            }
        },
        data: function() {
            return {

            }
        },
        methods: {

        }
    }
</script>


The rendering:

<div class="row">
    <!-- Country -->
    <geography-field
            label="Pa&iacute;s"
            :_default="selectedCountry"
            placeholder="Choose a country"
            name="country"
            :collection="countries"
            :onchange="selectCountry()"
    ></geography-field>
    <!-- Department -->
    <geography-field
            label="Departamento"
            :_default="selectedDepartment"
            :disabled="selectedCountry < 1"
            placeholder="Choose a department"
            name="department"
            :collection="departments"
            :onchange="selectDepartment()"
    ></geography-field>
    <!-- Location -->
    <geography-field
            label="Localidades"
            :_default="selectedLocation"
            :disabled="selectedDepartment < 1"
            placeholder="Choose a location"
            name="location"
            :collection="localities"
            :onchange="selectLocation()"
    ></geography-field>
    <!-- Zone -->
    <geography-field
            label="Zonas"
            :_default="selectedZone"
            :disabled="selectedLocation < 1"
            placeholder="Choose a zone"
            name="zone"
            :collection="zones"
    ></geography-field>
</div>

Edit: Including selectCountry():

selectCountry: function() {
        if(this.selectedCountry < 1) { return; }
        axios.get('/get_country/' + this.selectedCountry)
            .then(function (response) {
                var data = response.data;
                if(data.departments.length > 0) {
                    var departments = [];
                    $.each(data.departments, function (index, value) {
                        departments.push(value);
                    });
                    app.departments = departments;
                }
            });
    },

How I should do to pass a function to the component properly? Any suggestions are appreciated

Edit 2: Something I might cleared up is that the components are being rendered well. Just in case I will add the component registration:

Vue.component('geography-field', require('./components/GeographyField'));
...
const app = new Vue({

3 answers

  • answered 2018-02-13 02:29 Jacob Goh

    e.g. :onchange="selectCountry"

    Don't put () when you pass the function. It will trigger the function execution and whatever the function return will be pass into onchange.

  • answered 2018-02-13 03:09 btl

    I think you need to be wrapping the methods in the onchange property before you pass them to the component that creates the <geography-field> element. Without seeing your code I can only really best guess, but try the following:

    // in app.js
    methods: {
      method1 (item) {
        console.log(`method 1 got ${item}`)
      },
      method2 (item) {
        console.log(`method 2 got ${item}`)
      },
    }
    
    ...
    
    // I assume you have an array with all the objects that create each geography-field
    fields = [
      {
        id: 1,
        name: 'department',
        model: null,
        disabled: false,
        onchange: item => this.method1(item),
        collection: []
      },
      {
        id: 2,
        name: 'location',
        model: null,
        disabled: false,
        onchange: item => this.method2(item),
        collection: []
      }
    ]
    

    And as @JacobGoh mentioned don't use () on the onchange:

    <select class="form-control" :name="name" :id="name" :model="_default" :disabled="disabled" @change="onchange">
    

  • answered 2018-02-13 03:17 wang

    To pass a function as a prop to the component, you need to strip off the trailing parentheses of the function name. Otherwise, Vue will evaluate the function. For example:

    <geography-field
      label="Pa&iacute;s"
      :_default="selectedCountry"
      placeholder="Choose a country"
      name="country"
      :collection="countries"
      :onchange="selectCountry" // strip off the parentheses
    ></geography-field>
    

    But, I would also suggest you use $emit instead of passing a function. You can do that like so:

    The component definition:

    <template>
      <fieldset class="form-group col-md">
        <label :for="name" class="control-label" v-html="label"></label>
        <select class="form-control" :name="name" :id="name" :model="_default" :disabled="disabled" @change="onchange">
          <option :value="0" v-html="placeholder"></option>
          <option v-for="object in collection" :value="object.id">{{ object.name }}</option>
        </select>
      </fieldset>
    </template>
    
    <script>
    module.exports = {
      props: {
        label: { type: String, default: 'Options' },
        _default: { type: Number, default: 0 },
        disabled: { type: Boolean, default: false },
        placeholder: { type: String, default: 'Choose an option' },
        name: { type: String, required: true },
        collection: { type: Array, required: true }
      },
      methods: {
        onchange() {
          this.$emit("onchange"); 
        }
      }
    }
    </script>
    

    The component tag in the parent scope:

    <geography-field
      label="Pa&iacute;s"
      :_default="selectedCountry"
      placeholder="Choose a country"
      name="country"
      :collection="countries"
      @onchange="selectCountry" // use @
    ></geography-field>