Search code examples
javascriptvue.jsvuex

I'm trying to remake Vuex's Getting Started project using several components but can't figure out how to invoke root methods from components


The project in question is this one, simple and straightforward, but for the sake of learning I broke it down into index and 4 js files (parent, child, root and store) and I can't figure out how to invoke increment and decrement root methods in the child without using the commits inside the elements, or falling into bad practices or anti-patterns, such as using props or this.$root inside the elements.

Here's my code:

index.html:

<div id="app">
        <parent></parent>
</div>

root.js:

let vm = new Vue({
    el: '#app',
    store,
    methods:{
        increment(){
            store.commit('incrementar')
        },
        decrement(){
            store.commit('decrementar')
        } 
    }
})

store.js:

const store = new Vuex.Store({
    state:{
        numero: 11
    },
    mutations:{
        incrementar(state){
            state.numero++
        },
        decrementar(state){
            state.numero--
        }
    }
})

parent.js:

Vue.component('parent',{
    template:
    `
    <div>
        <h1>Numero: {{$store.state.numero}}</h1>
        <child></child>
    </div>
    `
})

child.js:

Vue.component('child',{
    template:
    `
    <div>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
        <h1>Numero: {{numero}}</h1>
    </div>
    `,
    computed: {
        numero() {
            return store.state.numero
        }
      }

I read that I should use events but I'm not sure exactly how with the buttons. It comes without saying that I'm pretty new to JavaScript, Vue and Vuex.


Solution

  • You can use instance methods events as it described here, for example:

    root.js:

    let vm = new Vue({
        el: '#app',
        store,
      mounted () {
        let self = this
        self.$root.$on('FancyEventName', self.HandleFancyEvent)
      },
      beforeDestroy () {
        let self = this
        self.$root.$off('FancyEventName', self.HandleFancyEvent)
      },
      methods: {
        HandleFancyEvent (arg) {
          let self = this
          if(arg === true){
           self.increment()
         } else{
           self.decrement()
         }
        },
        methods:{
            increment(){
                store.commit('incrementar')
            },
            decrement(){
                store.commit('decrementar')
            } 
        }
    })
    

    child.js:

    Vue.component('child',{
        template:
        `
        <div>
            <button @click="increment">+</button>
            <button @click="decrement">-</button>
            <h1>Numero: {{numero}}</h1>
        </div>
        `,
        methods:{
            increment(){
                let self = this
                self.$root.$emit('FancyEventName', true)
            },
            decrement(){
                let self = this
                self.$root.$emit('FancyEventName', false)
            } 
        }
        computed: {
            numero() {
                return store.state.numero
            }
          }
    

    It will work just fine. Also, I've seen some people taking approach of using separate instance to do the same thing.

    You can create an "EventBus" and use it like this:

    // event-bus.js
    import Vue from 'vue';
    const EventBus = new Vue();
    export default EventBus;
    

    And here is a component which emits an event:

    import Vue from 'vue';
    import EventBus from './event-bus';
    Vue.component('component-a', {
      ...
      methods: {
        emitMethod () {
           EventBus.$emit('EVENT_NAME', payLoad);
        }
      }
    });
    

    And a second component which reacts to this event:

    // component-b.js
    import Vue from 'vue';
    import EventBus from './event-bus';
    Vue.component(‘component-b’, {
      ...
      mounted () {
        EventBus.$on(‘EVENT_NAME’, function (payLoad) {
          ...
        });
      }
    });
    

    As you may see it does it the same way, the only difference is - you are using separate instance for communication purposes.

    I hope it helps! 🙂