Search code examples
javascripteventsvue.jscustom-events

Custom event not captured by component in Vue.js


Here is my components architecture

enter image description here

The component <ListButtonMenu> emits a custom event display-error as below. The emission works fine as I can see the event in VueDevtools:

<template>
    <div>
        <button type="button" class="btn btn-success" v-on:click="saveList()">
            Save
        </button>
    </div>
</template>

<script>
export default {
    props: {
        list: Object
    },
    methods: {
        saveList() {
            this.$emit('display-error');
        }
    }
}
</script>

enter image description here

The component <ErrorMessage> contains a div element which listen to the custom event and calls a method display(). The method changes the component CSS class in order to display himself: v-on:display-error="display". Unfortunately this does not work and the CSS class is not changed. What am I missing?

<template>
    <div id="errorMessage" v-bind:class="cssClass" v-on:display-error="display()">
        <div class="row justify-content-center alert alert-danger alert-dismissable text-danger">
            Error: {{ errorMessage }}
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">
                &times;
            </button>
        </div>
    </div>
</template>

<script>
export default {
    data: function () {
        return {
            'cssClass': 'd-none',
            'errorMessage': 'Hello'
        }
    },
    methods: {
        display() {
            this.cssClass = 'show';
            this.errorMessage='Goodbye';
        }
    }
}
</script>

Solution

  • Because my ErrorMessage and ListButtonMenu components do not have a parent-child relationship, I opted for a different solution than using custom events.

    Instead, I am using my application state (built using Vuex store) as an event bus to update an errorObject and the ErrorMessage component uses computed properties to capture the errorObject and displays himself.

    My component architecture remains the same as in the question. Here is the file store.js:

    import Vue from 'vue';
    import Vuex from 'vuex';
    
    Vue.use(Vuex);
    
    export const store = new Vuex.Store({
        state: {
            errorObject: {
                flag: false,
                message: ''
            }
        }
    });
    

    Here is the ListButtonMenu component:

    <template>
        <div>
            <button type="button" class="btn btn-success" v-on:click="saveList()">
                Save
            </button>
        </div>
    </template>
    
    <script>
    export default {
        props: {
            list: Object
        },
        methods: {
            saveList() {
                this.$store.state.errorObject.flag = true;
                this.$store.state.errorObject.message = 'My error mesage';
            }
        }
    }
    </script>
    

    And here the ErrorMessage component;

    <template>
        <div id="errorMessage" v-bind:class="cssClass">
            <div class="row justify-content-center alert alert-danger alert-dismissable text-danger">
                Error: {{ errorMessage }}
                <button type="button" class="close" v-on:click="close()">
                    &times;
                </button>
            </div>
        </div>
    </template>
    
    <script>
    export default {
        data: function () {
            return {
            }
        },
        computed: {
            cssClass() {
                if (this.$store.state.errorObject.flag) { return 'display' }
                else { return 'd-none' }
            },
            errorMessage() {
                return this.$store.state.errorObject.message;
            }
        },
        methods: {
            close() {
                this.$store.state.errorObject.flag = false;
                this.$store.state.errorObject.message= '';
            }
        }
    }
    </script>