How to call function on child component on parent events
Asked Answered
A

11

306

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.

Airing answered 6/3, 2017 at 18:14 Comment(1)
That hub model thing, is there documentation about it somewhere? Seems interesting, I haven't heard about it.Man
G
423

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.

Gd answered 2/8, 2017 at 14:25 Comment(17)
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 JKnob
a ref to the docs would be a helpful aswell vuejs.org/v2/guide/…Perlaperle
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!Diatomite
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? ThanksFredfreda
@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.Gd
Here in Brazil this is called "Gambiarra".Partook
@Knob Can you please explain how this answer makes coupling and the R Joy's answer does not?Critic
@Knob 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 callLasandralasater
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.Tyrus
Is this still the right answer for Vue 3?Madid
@MattSanders just updated the answer for Vue 3Gd
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?Joinery
@Joinery 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)Gd
@Gd 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.Joinery
@Joinery 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.Dextrogyrate
That's not possible in Vue3, they removed the functionality.Joinery
The problem is the this.$refs.child is undefined if <ChildComponent v-if=""/> contains an if-statement. You can use a v-show, but it doesn't support the v-else.Drench
C
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.

Colorblind answered 6/3, 2017 at 19:27 Comment(15)
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.Logomachy
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 flagAlkaloid
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.Colorblind
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.Domel
As state by @ИльяЗеленько, it does happen quite often, it would be a godsend right about now.Kempis
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.Rileyrilievo
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.Christo
@BenWinding Use the second suggestion: pass a bus to the child as a prop.Colorblind
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.Christo
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.Thug
@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.Colorblind
@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.Thug
@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.Colorblind
Not working when collection is set to [] empty using filter in place. In this case no change is triggered on child end.Warhol
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.Joinery
M
48

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/

Murraymurre answered 6/3, 2017 at 20:23 Comment(8)
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?Airing
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: vuejs.org/v2/guide/…Murraymurre
This creates a coupling between the child and parent and is considered bad practiceKmeson
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.Abbevillian
@Kmeson Can you please explain how this answer makes coupling and the R Joy's answer does not?Critic
@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.Kmeson
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 componentMarsland
This is not working on Vue 3. Vue 3 removed $on event: v3.vuejs.org/guide/migration/events-api.html#_3-x-updateBayern
D
19

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/[email protected]/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.

Doge answered 30/10, 2018 at 8:12 Comment(3)
I like where this solution is going but what exactly is "this.setter" in the parent?Pascia
It’s the setValue function reference emitted by the child component as an argument to handler event.Doge
only example i have found that actually works ! thanksDapple
I
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>
Inexpedient answered 5/3, 2018 at 6:29 Comment(4)
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 eventGae
@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!Inexpedient
I didn't see it. Thanks for the clarificationGae
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.Pascia
C
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>
Charity answered 30/10, 2018 at 8:35 Comment(2)
This does not work for Vue3 anymore.Drudgery
This should be the accepted answer for Vue2Esta
J
7

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

Jala answered 25/9, 2017 at 14:23 Comment(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" changesSilvers
using a 'global' state for a communication line between 2 components is one of the worst solutions. Please don't do this.Extended
J
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")
            }
    }
June answered 28/12, 2020 at 6:44 Comment(0)
P
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

Pas answered 26/7, 2018 at 13:57 Comment(1)
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.Humiliate
C
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

Chlorothiazide answered 10/11, 2019 at 0:33 Comment(1)
This is a super smart trick! Changing :key indeed forces repaint. Simple, yet powerful. Thanks!Ralaigh
L
2

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");
    }
  }
}
Ld answered 3/3, 2021 at 13:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.