297

Context

In Vue 2.0 the documentation and others clearly indicate that communication from parent to child happens via props.

Question

How does a parent tell its child an event has happened via props?

Should I just watch a prop called event? That doesn't feel right, nor do alternatives ($emit/$on is for child to parent, and a hub model is for distant elements).

Example

I have a parent container and it needs to tell its child container that it's okay to engage certain actions on an API. I need to be able to trigger functions.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
jbodily
  • 3,613
  • 2
  • 20
  • 21

11 Answers11

407

Vue 3 Composition API

Create a ref for the child component, assign it in the template, and use the <ref>.value to call the child component directly.

<script setup>
import {ref} from 'vue';

const childComponentRef = ref(null);

function click() {
  // `childComponentRef.value` accesses the component instance
  childComponentRef.value.doSomething(2.0);
}
</script>

<template>
  <div>
    <child-component ref="childComponentRef" />
    <button @click="click">Click me</button>
  </div>
</template>

Couple things to note-

  • If your child component is using <script setup>, you'll need to declare public methods (e.g. doSomething above) using defineExpose.
  • If you're using Typescript, details of how to type annotate this are here.

Vue 3 Options API / Vue 2

Give the child component a ref and use $refs to call a method on the child component directly.

html:

<div id="app">
  <child-component ref="childComponent"></child-component>
  <button @click="click">Click</button>  
</div>

javascript:

var ChildComponent = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  }
}

new Vue({
  el: '#app',
  components: {
    'child-component': ChildComponent
  },
  methods: {
    click: function() {
        this.$refs.childComponent.setValue(2.0);
    }
  }
})

For more info, see Vue 3 docs on component refs or Vue 2 documentation on refs.

joerick
  • 16,078
  • 4
  • 53
  • 57
  • 27
    This way parent and child components become coupled. For real events, say when you can't just change a prop to trigger an action, I would go with the bus solution suggested by @Roy J – Jared Apr 19 '18 at 17:12
  • 4
    a ref to the docs would be a helpful aswell https://vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements – ctf0 Aug 21 '18 at 01:22
  • In my child component, for special reasons, I had to use v-once to terminate the reactivity. Thus passing the prop down from parent to child wasn't an option, so this solution did the trick! – John Sep 15 '18 at 03:12
  • 1
    _newbie question:_ Why use `ref` instead of _creating a prop, that watches its value then emit it to another function in parent?_ I mean it does has a lot of things to do, but is using `ref` even safe? Thanks – Irfandy Jip Jan 21 '19 at 07:18
  • 12
    @IrfandyJip - yes, `ref` is safe. Generally, it's discouraged because the Vue community prefers to pass state to children, and events back to the parent. Generally speaking, this leads to more isolated, internally-consistent components (a good thing™). But, if the information you're passing to the child really is an *event* (or a command), modifying state isn't the right pattern. In that case, calling a method using a `ref` is totally fine, and it's not going to crash or anything. – joerick Feb 06 '19 at 09:58
  • 1
    Here in Brazil this is called "Gambiarra". – Renan Coelho May 17 '19 at 03:19
  • @Jared Can you please explain how this answer makes coupling and the R Joy's answer does not? – Abdul Rehman Jul 25 '19 at 09:57
  • 4
    @Jared all applications require _some_ level of coupling. Often a small direct coupling between parent and child is fine if it simplifies the code - introducing an entire message bus may introduce a superfluous amount of complexity into the system when all that was needed was a single function call – jonny Jun 19 '20 at 11:56
  • There is nothing wrong with two components being coupled provided that they are, in fact ...coupled. If you have two components that rely on each other by nature (such as a cart drawer and a cart drawer line. You aren't going to use a cart drawer line anywhere else in your application) it's not unreasonable for them to be coupled. Sure, it may not be optimal. If you refactored the child, and rename it's method, you'll have to make the change in the parent. But this is always the case for emitted event names, prop names, etc. In certain situations it's not unreasonable for them to be coupled. – Air Jun 15 '21 at 12:15
  • Is this still the right answer for Vue 3? – Matt Sanders Nov 08 '22 at 22:23
  • 1
    @MattSanders just updated the answer for Vue 3 – joerick Nov 09 '22 at 09:03
  • This calling exposed functions on refs to child elements does indeed seem to be the "official" way to send a "command" from parent to child component in Vue 3. Semantically, though, it's fundamentally the same as an event, and as there are already events from child to parent, I'm a little baffled as to why Vue doesn't just officially support parent to child events too. If it's good in one direction, why not in the other? – Jez Nov 10 '22 at 20:31
  • @Jez in fact, that was the way that Vue v1 worked. But Evan and the community realised that in fact, the pattern of state flowing downwards and events flowing upwards actually made apps much easier to maintain, generally speaking. (And for the 1% of times that you need to send events downwards, methods-on-refs works fine) – joerick Nov 24 '22 at 14:38
  • @joerick You absolutely do need to send events down. I've been working on a message popup and if two messages are sent in quick succession, the state changes twice and the message component watcher only sees the second change. I'd think there are a bunch more use cases when watching state just doesn't cut it. – Jez Nov 25 '22 at 15:49
  • @Jez Not necessarily, you can also use an event bus, where your child component listens to events that are being emitted by the parent. Thats how I used to do it in Vue2. Create another Vue instance as an event bus and let deeply nested child components listen to certain events. – Edito Feb 17 '23 at 11:13
  • That's not possible in Vue3, they removed the functionality. – Jez Feb 17 '23 at 11:44
  • The problem is the this.$refs.child is undefined if contains an if-statement. You can use a v-show, but it doesn't support the v-else. – sparkle May 31 '23 at 14:09
114

What you are describing is a change of state in the parent. You pass that to the child via a prop. As you suggested, you would watch that prop. When the child takes action, it notifies the parent via an emit, and the parent might then change the state again.

var Child = {
  template: '<div>{{counter}}</div>',
  props: ['canI'],
  data: function () {
    return {
      counter: 0
    };
  },
  watch: {
    canI: function () {
      if (this.canI) {
        ++this.counter;
        this.$emit('increment');
      }
    }
  }
}
new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  data: {
    childState: false
  },
  methods: {
    permitChild: function () {
      this.childState = true;
    },
    lockChild: function () {
      this.childState = false;
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div id="app">
<my-component :can-I="childState" v-on:increment="lockChild"></my-component>
<button @click="permitChild">Go</button>
</div>

If you truly want to pass events to a child, you can do that by creating a bus (which is just a Vue instance) and passing it to the child as a prop.

Roy J
  • 42,522
  • 10
  • 78
  • 102
  • 4
    I think this is the only answer in line with the official Vue.JS style guide and best practices. If you use the shorthand `v-model` on the component, you can also easily reset the value by emitting the corresponding event with less code. – Falco Oct 09 '17 at 15:16
  • For example, I want to give an alert when a user clicks a button. Do you propose for example: - watch a flag - set this flag from 0 to 1 when a click occurs, - do something - reset flag – Sinan Erdem Nov 23 '17 at 14:34
  • I would not expect the child to raise an alert for a click in the parent, but if that is what you wanted to do, I would pass a bus to the child and have the parent emit on the bus. – Roy J Nov 23 '17 at 18:22
  • 23
    It is very uncomfortable, you have to create an extra `prop` in a child, an extra property in `data`, then add `watch`... It would be comfortable if there was built-in support to somehow transfer events from parent to child. This situation occurs quite often. – Илья Зеленько Oct 31 '18 at 16:02
  • 2
    As state by @ИльяЗеленько, it does happen quite often, it would be a godsend right about now. – Stark Jul 08 '19 at 20:28
  • I did *not* think the props way could work for me as my child components were created in a v-for loop against a computed array that would change based on filters. But I placed the additional props I needed in a separate data array in the parent, bound the component property to the proper separate array item, then placed a watcher in the child component and wow - it worked and super fast too. – saswanb Oct 23 '19 at 18:23
  • This doesn't work for me as I want to send a 'void' message, to trigger something in the child. Using this method I need to _reset_ the state everytime. – Ben Winding Jun 26 '20 at 08:29
  • @BenWinding Use the second suggestion: pass a bus to the child as a prop. – Roy J Jun 28 '20 at 14:52
  • 1
    Thanks @RoyJ, I guess that requires the bus prop to exist when the child subscribes to it though, I suppose the whole idea of sending events down to children is discouraged in Vue. – Ben Winding Jun 29 '20 at 00:21
  • I bumped into this in a very simple case: each child has its own state but I want the parent component to have a button that updates the state of *all* children when clicked. That is clearly best modelled as an event, not a prop. All of these solutions just feel like event passing with extra steps. Not sure why Vue made this choice. – Mattias Martens Aug 21 '20 at 04:55
  • @MattiasMartens So pass a bus. The same bus can go to all the children. One event on the bus is received by all the children. – Roy J Aug 21 '20 at 20:00
  • @RoyJ Vue's convention is that events are for child-to-parent communication and props are for parent-to-child communication. Do you think an event bus is in line with that? It's true it is passed as a prop, but the events it emits are not props. It seems more like a hack to get around a questionable choice than anything else. It's a hack I don't think should be necessary in the first place. – Mattias Martens Aug 22 '20 at 07:50
  • @MattiasMartens Conventions are for normal, common behaviors. Beyond that, it's a matter of the right tool for the right job. A bus is a tool. In your example, it may make sense to move the state up from the children into the parent where the event happens. But if what you describe, event in parent, is what you want, a common bus is a sensible solution. – Roy J Aug 26 '20 at 16:54
  • Not working when collection is set to [] empty using filter in place. In this case no change is triggered on child end. – Stan Sokolov Oct 20 '20 at 18:41
  • No, this is fundamentally the wrong approach. State changes and events are fundamentally different things, hence the existence of events and props as two different things in Vue. It's not sensible or semantically correct IMHO to represent an event being sent to a child element as a "state change". You can hack it in but it looks weird, because it's not really right. Something like "reset display to default appearance" is an event, it is not a state change. – Jez Nov 10 '22 at 20:36
47

You can use $emit and $on. Using @RoyJ code:

html:

<div id="app">
  <my-component></my-component>
  <button @click="click">Click</button>  
</div>

javascript:

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  },
  created: function() {
    this.$parent.$on('update', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
    click: function() {
        this.$emit('update', 7);
    }
  }
})

Running example: https://jsfiddle.net/rjurado/m2spy60r/1/

rjurado01
  • 5,337
  • 3
  • 36
  • 45
  • 7
    I'm surprised it works. I thought emitting to a child was an anti-pattern, or that the intent was for emit to only be from child to parent. Are there any potential problems with going the other way? – jbodily Mar 06 '17 at 20:44
  • 2
    This may not be considered the best way, I don't know, but If you know what are you doing I thing threre is not a problem. The other way is use central bus: https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication – rjurado01 Mar 07 '17 at 07:24
  • 24
    This creates a coupling between the child and parent and is considered bad practice – morrislaptop Sep 12 '17 at 16:14
  • 7
    This only works because the parent is not a component but actually a vue app. In reality this is using the vue instance as a bus. – Julio Rodrigues Sep 14 '17 at 14:57
  • @morrislaptop Can you please explain how this answer makes coupling and the R Joy's answer does not? – Abdul Rehman Jul 25 '19 at 09:56
  • 3
    @Bsienn the call to this.$parent makes this component dependent on the parent. uses $emit to and props so the only dependencies are through Vue's communication system. This approach allows the same component to be used anywhere in the component hierarchy. – morrislaptop Jul 27 '19 at 02:59
  • 2
    I think that in some cases this is better than calling the function from the parent, because in this way you manage the execution of the code inside the component that declares it, even if it creates a coupling with the parent component, while calling the function from the parent takes the control away from the child component – Giorgio Tempesta Feb 12 '20 at 11:02
  • 2
    This is not working on Vue 3. Vue 3 removed $on event: https://v3.vuejs.org/guide/migration/events-api.html#_3-x-update – Benny Chan Oct 31 '21 at 10:27
16

A simple decoupled way to call methods on child components is by emitting a handler from the child and then invoking it from parent.

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
   setValue(value) {
     this.value = value;
    }
  },
  created() {
    this.$emit('handler', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
   setValueHandler(fn) {
     this.setter = fn
    },
    click() {
     this.setter(70)
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

<div id="app">
  <my-component @handler="setValueHandler"></my-component>
  <button @click="click">Click</button>  
</div>

The parent keeps track of the child handler functions and calls whenever necessary.

nilobarp
  • 3,806
  • 2
  • 29
  • 37
11

Did not like the event-bus approach using $on bindings in the child during create. Why? Subsequent create calls (I'm using vue-router) bind the message handler more than once--leading to multiple responses per message.

The orthodox solution of passing props down from parent to child and putting a property watcher in the child worked a little better. Only problem being that the child can only act on a value transition. Passing the same message multiple times needs some kind of bookkeeping to force a transition so the child can pick up the change.

I've found that if I wrap the message in an array, it will always trigger the child watcher--even if the value remains the same.

Parent:

{
   data: function() {
      msgChild: null,
   },
   methods: {
      mMessageDoIt: function() {
         this.msgChild = ['doIt'];
      }
   }   
   ...
}

Child:

{
   props: ['msgChild'],
   watch: {
      'msgChild': function(arMsg) {
         console.log(arMsg[0]);
      }
   }
}

HTML:

<parent>
   <child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
tony19
  • 125,647
  • 18
  • 229
  • 307
Jason Stewart
  • 374
  • 4
  • 7
  • 1
    I think this won't work if msgChild has always the same status on the parent. For example: I want a component that opens a modal. The parent doesn't care if the current status is open or close, it just wants to open the modal at any moment. So, if the parent does this.msgChild = true; the modal is closed, and then the parent does this.msgChild = true, the child won't receive the event – Jorge Sainz Nov 19 '18 at 10:34
  • 1
    @JorgeSainz: That is why I'm wrapping the value in an array prior to assigning it to the data item. Without wrapping the value in an array, it behaves just as you specify. So, msgChild = true, msgChild = true -- no event. msgChild = [true], msgChild = [true] -- event! – Jason Stewart Nov 20 '18 at 11:20
  • 1
    I didn't see it. Thanks for the clarification – Jorge Sainz Nov 20 '18 at 14:01
  • 1
    This is cool, but feels a little hackish. I'm going to use it since it's the cleaner than using the component ref hack and less complicated that the event bus solution. I know that vue wants decoupling and only allow state changes to effect the component but there should be some builtin way to call a child's methods if needed. Perhaps a modifier on a prop that once it changes state you could automatically reset it to a default value so that the watcher is ready for the next state change. Anyway thanks for posting your find. – Craig Jul 05 '19 at 15:51
11

The below example is self explainatory. where refs and events can be used to call function from and to parent and child.

// PARENT
<template>
  <parent>
    <child
      @onChange="childCallBack"
      ref="childRef"
      :data="moduleData"
    />
    <button @click="callChild">Call Method in child</button>
  </parent>
</template>

<script>
export default {
  methods: {
    callChild() {
      this.$refs.childRef.childMethod('Hi from parent');
    },
    childCallBack(message) {
      console.log('message from child', message);
    }
  }
};
</script>

// CHILD
<template>
  <child>
    <button @click="callParent">Call Parent</button>
  </child>
</template>

<script>
export default {
  methods: {
    callParent() {
      this.$emit('onChange', 'hi from child');
    },
    childMethod(message) {
      console.log('message from parent', message);
    }
  }
}
</script>
Mukundhan
  • 3,284
  • 23
  • 36
7

If you have time, use Vuex store for watching variables (aka state) or trigger (aka dispatch) an action directly.

brightknight08
  • 546
  • 5
  • 14
  • 2
    due to reactivity of vuejs/vuex that is the best aproach, in parent make a action/mutation that change a vuex property value and in child have a computed value that get this same vuex $store.state.property.value or a "watch" method that do something when vuex "$store.state.property.value" changes – FabianSilva Dec 07 '17 at 15:21
  • using a 'global' state for a communication line between 2 components is one of the worst solutions. Please don't do this. – Niels Lucas May 02 '22 at 09:48
6

Calling child component in parent

<component :is="my_component" ref="my_comp"></component>
<v-btn @click="$refs.my_comp.alertme"></v-btn>

in Child component

mycomp.vue

    methods:{     
    alertme(){
            alert("alert")
            }
    }
Balaji
  • 9,657
  • 5
  • 47
  • 47
2

I think we should to have a consideration about the necessity of parent to use the child’s methods.In fact,parents needn’t to concern the method of child,but can treat the child component as a FSA(finite state machine).Parents component to control the state of child component.So the solution to watch the status change or just use the compute function is enough

  • 4
    If you're saying "parents should never concern themselves with controlling the children", there are cases where this is necessary. Consider a countdown timer component. The parent may wish to reset the timer to start over. Simply using props isn't enough because going from time=60 to time=60 will not modify the prop. The timer should expose a 'reset' function that the parent can call as appropriate. – tbm Apr 15 '20 at 16:53
2

you can use key to reload child component using key

<component :is="child1" :filter="filter" :key="componentKey"></component>

If you want to reload component with new filter, if button click filter the child component

reloadData() {            
   this.filter = ['filter1','filter2']
   this.componentKey += 1;  
},

and use the filter to trigger the function

Ardian Zack
  • 49
  • 1
  • 4
1

You can simulate sending event to child by toggling a boolean prop in parent.

Parent code :

...
<child :event="event">
...
export default {
  data() {
    event: false
  },
  methods: {
    simulateEmitEventToChild() {
      this.event = !this.event;
    },
    handleExample() {
      this.simulateEmitEventToChild();
    }
  } 
}

Child code :

export default {
  props: {
    event: {
      type: Boolean
    }
  },
  watch: {
    event: function(value) {
      console.log("parent event");
    }
  }
}
abdelgrib
  • 843
  • 7
  • 11