Pass event from one component to other components - vue.js

I'm really new to Vue and can't seem to get how an event is passed from one component to other components. I'm currently using v-blur and I want to blur every component except the one clicked. I figured by passing an event to the other components when the original component is clicked on i could get the effect wanted. Any help is much appreciated!
// Parent.vue
<template>
<div id="Parent">
<child-one #toggle-blur="toggleChildBlur"/>
<child-two #toggle-blur="toggleChildBlur"/>
<child-three #toggle-blur="toggleChildBlur"/>
</div>
</template>
<script>
import ChildOne from './ChildOne'
import ChildTwo from './ChildTwo'
import ChildThree from './ChildThree'
export default {
name: 'Parent',
components: {
ChildOne, ChildTwo, ChildThree
},
methods: {
toggleChildBlur () {
// Blur every child except the clicked one?
}
},
data () {
return {}
}
}
</script>
// ChildOne.vue, basically the same for two and three aswell
<template>
<div id="child-one" v-blur="blurConfig" #click="$emit('toggle-blur')"></div>
</template>
<script>
export default {
name: 'ChildOne',
methods: {
toggleBlur () {
this.blurConfig.isBlurred = !this.blurConfig.isBlurred;
}
},
data () {
return {
blurConfig: {
isBlurred: false,
opacity: 0.3,
filter: 'blur(1.2px)',
transition: 'all .3s linear'
}
}
}
}
</script>

Events dispatched in Vue travel in one direction: child ⇒ parent. If you have a component P (parent) and child C1 (child 1) and C2 (child 2), there is no way to trigger event in C1 and send it to C2. It will go to P.
If you have very nested structure (many levels) and you really need to do so, the easiest way to do it is to dispatch and listen for events on something that is not part of the display list, i.e. something global. Very typical solution is to have the so called "Event Bus" - a separate dummy Vue instance, that you use only for events. Here's a full tutorial about Global Event Bus in Vue.
It looks something like this:
// in some global file
const EventBus = new Vue();
// in GC1 (parent -> child 1 -> grand child 1)
EventBus.$emit('someEvent', 'some-data')
// in GC5 (parent -> child 3 -> grand child 5)
EventBus.$on('someEvent', function(data) {
console.log(data) // 'some-data
})
This way you can easily dispatch/catch events all over the place.
Good luck! :)

I figured out a way to get the effect i wanted in the end. My solution might not be very scalable but works for now! I pass the child index from the emitter and loop through to blur each component except the clicked child index.
// ChildOne.vue
// Basically the same for two and three as well except sending corresponding index
// on click event.
// Click event is now sending the index of the component to know which one got clicked.
<template>
<div id="child-one" #click="$emit('toggle-blur', 0)"></div>
</template>
// Parent.vue
// Every child component now gets their separate blur config.
// When child is clicked the index of the child now gets sent to help skip and blur
// the other components.
<template>
<div id="parent">
<child-one v-blur="blurConfig[0]" #toggle-blur="toggleChildBlur"/>
<child-two v-blur="blurConfig[1]" #toggle-blur="toggleChildBlur"/>
<child-three v-blur="blurConfig[2]" #toggle-blur="toggleChildBlur"/>
</div>
</template>
<script>
import ChildOne from './ChildOne'
import ChildTwo from './ChildTwo'
import ChildThree from './ChildThree'
export default {
name: 'Parent',
components: {
ChildOne, ChildTwo, ChildThree
},
methods: {
toggleChildBlur (childIndex) {
// Unblur if click event is outside focused component
if (this.blurConfig[childIndex].isBlurred) {
for (let i = 0; i < this.blurConfig.length; i++) {
this.blurConfig[i].isBlurred = false
}
} else {
for (let i = 0; i < this.blurConfig.length; i++) {
if (i !== childIndex) {
this.blurConfig[i].isBlurred = !this.blurConfig[i].isBlurred
}
}
}
}
},
data () {
return {
// Blur settings for each component
blurConfig: [
{
isBlurred: false,
opacity: 0.2,
filter: 'blur(1.2px)',
transition: 'all .2s linear'
},
{
isBlurred: false,
opacity: 0.2,
filter: 'blur(1.2px)',
transition: 'all .2s linear'
},
{
isBlurred: false,
opacity: 0.2,
filter: 'blur(1.2px)',
transition: 'all .2s linear'
}
]
}
}
}
</script>

Related

Vue: emit to view component works only the first time

I have a view which has a recursive component A, which in turn embeds a component B. In component B changes are made, which should be passed via emit to component A and these should be passed directly to the view.
This "chain" works, but only the first time:
Komponent B:
// Emit in component B
function emitToParent() {
console.log("component B: ", props.current.id);
emits('changeParentReactiveData', props.current.id, data);
}
Komponent A:
// Receiving the emit in Komponent A and passing it to the view
<component-b #change-parent-reactive-data="emitToParent" />
function emitToParent(id, data) {
console.log("component A: ", id);
emits("changeParentReactiveData", id, data);
}
View:
// Receiving the emit in the View
<component-a #change-parent-reactive-data="setReactive" />
function setReactive(id, data) {
console.log("view: ", id);
}
Outcome console.log:
component B: 262194
component A: 262194
view: 262194
component B: 262187
component A: 262187
component B: 262193
component A: 262193
So as you can see, the last emit to the view component happens only the first time. Why? Does anyone have an idea?
Your code should work, As I am not able to setup the Vue 3 in the below code snippet. I am demoing it using Vue 2 setup. Please have a look and try to find out the root cause of the issue you are facing.
Every time you will click on Click Me text in the inner child component. It is emitting the events properly.
Vue.component('childComponent', {
template: `<div class="emit-time" #click="click()">Click me (Component A)</div>`,
methods: {
click() {
console.log('component A');
this.$emit('clickup', 'a click');
}
}
});
Vue.component('ParentComponent', {
data() {
return {
message: null
}
},
template: `<div><p v-text="message"></p><child-component #clickup="localMethod"></child-component></div>`,
methods: {
localMethod() {
console.log('component B');
this.message = 'Component B invoked and emitted the event to the parent';
this.$emit('clickup', 'a click');
}
}
});
new Vue({
el: "#app",
data: {
message: null
},
methods: {
localMethod() {
console.log('Root component');
this.message = 'Top most parent invoked'
}
}
});
#app {
padding : 20px;
}
div {
padding : 10px;
border : 1px dashed black;
}
div ~ div {
margin-top : 10px;
border-color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-text="message"></p>
<parent-component #clickup="localMethod"></parent-component>
</div>
Your code should work as it is. I have set up a demo here and it is working just fine. Maybe you can match your code with this-
View.vue
<script setup>
import Comp1 from './Comp1.vue'
const setReactive = (payload) => {
console.log('View', payload);
};
</script>
<template>
<v-app>
<v-main>
<Comp1 #change-parent-reactive-data="setReactive" />
</v-main>
</v-app>
</template>
Component 1
<script setup>
import Comp2 from './Comp2.vue'
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = (payload) => {
console.log('Component A', payload);
emits('changeParentReactiveData', payload);
};
</script>
<template>
<Comp2 #change-parent-reactive-data="emitToParent" :some-prop="262194"></Comp2>
</template>
Component 2
<script setup>
const props = defineProps(['someProp']);
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = () => {
console.log('Component B', props.someProp);
emits('changeParentReactiveData', props.someProp);
};
</script>
<template>
<v-btn #click="emitToParent()">
click
</v-btn>
</template>
Try implementing a global event bus to pass values from component to component.
Here's a blog to explain how:
https://blog.logrocket.com/using-event-bus-in-vue-js-to-pass-data-between-components/

VueJS 2.x Child-Component doesn't react to changed parent-property

I have the problem, that a component doesn't recognize the change of a property.
The component is nested about 5 levels deep. Every component above the faulty one does update with the same mechanics and flawlessly.
I invested some time to get to the problem, but I can't find it.
The flow is:
Dashboard (change value and pass as prop)
TicketPreview (Usage and
pass prop)
CommentSection (Pass prop)
CommentList (FAULTY / Usage of prop)
Everything down to the commentSection is being updated as expected, but the commentList doesn't get the update notification (beforeUpdate doesn't get triggered).
Since I tested quite a few things I will only post the essential code from commentSection (parent) and commenList (child)
DISCLAIMER: This is a prototype code without backend, therefore typical API-Requests are solved with the localStorage of the users browser.
commentSection
<template>
<div id="comment-section">
<p>{{selectedTicket.title}}</p>
<comment-form :selectedTicket="selectedTicket" />
<comment-list :selectedTicket="selectedTicket" />
</div>
</template>
<script>
import CommentForm from "#/components/comment-section/CommentForm";
import CommentList from "#/components/comment-section/CommentList";
export default {
name: "CommentSection",
components: {
CommentForm,
CommentList,
},
props: {
selectedTicket: Object,
},
beforeUpdate() {
console.log("Comment Section");
console.log(this.selectedTicket);
},
updated() {
console.log("Comment Section is updated");
}
}
</script>
CommentList
<template>
<div id="comment-list">
<comment-item
v-for="comment in comments"
:key="comment.id"
:comment="comment"
/>
</div>
</template>
<script>
import CommentItem from "#/components/comment-section/CommentItem";
export default {
name: "CommentList",
components: {
CommentItem,
},
data() {
return {
comments: Array,
}
},
props: {
selectedTicket: Object,
},
methods: {
getComments() {
let comments = JSON.parse(window.localStorage.getItem("comments"));
let filteredComments = [];
for(let i = 0; i < comments.length; i++){
if (comments[i].ticketId === this.selectedTicket.id){
filteredComments.push(comments[i]);
}
}
this.comments = filteredComments;
}
},
beforeUpdate() {
console.log("CommentList");
console.log(this.selectedTicket);
this.getComments();
},
mounted() {
this.$root.$on("updateComments", () => {
this.getComments();
});
console.log("CL Mounted");
},
}
</script>
The beforeUpdate() and updated() hooks from the commentList component are not being fired.
I guess I could work around it with an event passing the data, but for the sake of understanding, let's pretend it's not a viable option right now.
It would be better to use a watcher, this will be more simple.
Instead of method to set comments by filtering you can use computed property which is reactive and no need to watch for props updates.
CommentSection
<template>
<div id="comment-section">
<p>{{ selectedTicket.title }}</p>
<comment-form :selectedTicket="selectedTicket" />
<comment-list :selectedTicket="selectedTicket" />
</div>
</template>
<script>
import CommentForm from "#/components/comment-section/CommentForm";
import CommentList from "#/components/comment-section/CommentList";
export default {
name: "CommentSection",
components: {
CommentForm,
CommentList
},
props: {
selectedTicket: Object
},
methods: {
updateTicket() {
console.log("Comment section is updated");
console.log(this.selectedTicket);
}
},
watch: {
selectedTicket: {
immediate: true,
handler: "updateTicket"
}
}
};
</script>
CommentList
<template>
<div id="comment-list">
<comment-item
v-for="comment in comments"
:key="comment.id"
:comment="comment"
/>
</div>
</template>
<script>
import CommentItem from "#/components/comment-section/CommentItem";
export default {
name: "CommentList",
components: {
CommentItem
},
props: {
selectedTicket: Object
},
computed: {
comments() {
let comments = JSON.parse(window.localStorage.getItem("comments"));
let filteredComments = [];
for (let comment of comments) {
if (comment.ticketId == this.selectedTicket.id) {
filteredComments.push(comment);
}
}
// // using es6 Array.filter()
// let filteredComments = comments.filter(
// (comment) => comment.ticketId == this.selectedTicket.id
// );
return filteredComments;
}
}
};
</script>
I found the problem: Since commentList is only a wrapper that doesn't use any of the values from the prop, the hooks for beforeUpdate and updated are never triggered. The Vue Instance Chart is misleading in that regard. The diagram shows it like beforeUpdate would ALWAYS fire, when the data changed (then re-render, then updated), but beforeUpdate only fires if the Component and Parent has to be re-rendered.
The Object updates as expected, it just never triggered a re-render on the child component because the wrapper has not been re-rendered.

Vue component wrapping

What is the correct way to wrap a component with another component while maintaining all the functionality of the child component.
my need is to wrap my component with a container, keeping all the functionality of the child and adding a trigger when clicking on the container outside the child that would trigger the child`s onclick event,
The parent component should emit all the child component events and accept all the props the child component accepts and pass them along, all the parent does is add a clickable wrapper.
in a way im asking how to extend a component in vue...
It is called a transparent wrapper.
That's how it is usually done:
<template>
<div class="custom-textarea">
<!-- Wrapped component: -->
<textarea
:value="value"
v-on="listeners"
:rows="rows"
v-bind="attrs"
>
</textarea>
</div>
</template>
<script>
export default {
props: ['value'], # any props you want
inheritAttrs: false,
computed: {
listeners() {
# input event has a special treatment in this example:
const { input, ...listeners } = this.$listeners;
return listeners;
},
rows() {
return this.$attrs.rows || 3;
},
attrs() {
# :rows property has a special treatment in this example:
const { rows, ...attrs } = this.$attrs;
return attrs;
},
},
methods: {
input(event) {
# You can handle any events here, not just input:
this.$emit('input', event.target.value);
},
}
}
</script>
Sources:
https://www.vuemastery.com/conferences/vueconf-us-2018/7-secret-patterns-vue-consultants-dont-want-you-to-know-chris-fritz/
https://zendev.com/2018/05/31/transparent-wrapper-components-in-vue.html

VUEJS 2: Events. Parent to trigger an method found in a child component [duplicate]

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.
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.
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.
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/
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.
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>
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>
If you have time, use Vuex store for watching variables (aka state) or trigger (aka dispatch) an action directly.
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")
}
}
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
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
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");
}
}
}

Building reusable components with custom methods

I am trying to build a reusable tab component with vuejs. I am still learning some basic consepts of vue and hardly managed to finish tab generation and switch logic. It is OK to switch between tabs in component itself now. But I have some problems with making my component to listen outside triggers.
For now, I can switch my tabs outside of the component with the help of $refs. But as I am trying to make it reusable this method doesn't sound practical. What should I do?
Here is the JSFiddle
Vue.component('tabs', {
template: `
<div class="tabs">
<div class="tab-titles">
<a :class="{'active':tab.active}" v-for="tab in tablist" href="#" class="tab-title" #click.prevent="activateTab(tab)">{{tab.title}}</a>
</div>
<div class="tab-contents">
<slot></slot>
</div>
</div>
`,
data() {
return {
tablist: [],
}
},
methods: {
activateTab: function(tab) {
this.tablist.forEach(t => {
t.active = false;
t.tab.is_active = false
});
tab.active = true;
tab.tab.is_active = true;
},
activateTabIndex: function(index) {
this.activateTab(this.tablist[index]);
},
collectTabData: function(tabData) {
this.tablist.push(tabData)
}
},
});
//==============================================================================
Vue.component('tab', {
template: `
<div :class="{'active':is_active}" class="tab-content">
<slot></slot>
</div>
`,
data() {
return {
is_active: this.active
}
},
mounted() {
this.$parent.collectTabData({
tab: this,
title: this.title,
active: this.active,
is_active: this.is_active
});
},
props: {
title: {
type: String,
required: true
},
active: {
type: [Boolean, String],
default: false
},
}
});
//==============================================================================
Vue.component('app', {
template: `
<div class="container">
<tabs ref="foo">
<tab title="tab-title-1">
<h3>content-1</h3>
Initial content here
</tab>
<tab title="tab-title-2" active>
<h3>content-2</h3>
Some content here
</tab>
<tab title="tab-title-3">
<h3>content-3</h3>
Another content here
</tab>
</tabs>
<a href="#" #click='switchTab(0)'>switch to tab(index:0)</a>
</div>
`,
methods: {
switchTab: function () {
vm.$children[0].$refs.foo.activateTabIndex(0);
}
},
});
//==============================================================================
const vm = new Vue({
el: '#inner-body',
});
#import url('https://fonts.googleapis.com/css?family=Lato');
#inner-body{
font-family: 'Lato', sans-serif;
background-color:#ffffff;
padding:20px;
}
.tab-titles {
}
.tab-title {
display: inline-block;
margin-right: 10px;
color: #bbb;
text-decoration: none;
}
.tab-title.active {
color: #06cd92;
border-bottom:1px solid #06cd92;
}
.tab-contents{
border: 1px solid #ddd;
border-width:1px 0;
margin-top:-1px;
margin-bottom:20px;
}
.tab-content {
display: none;
padding-bottom:20px;
}
.tab-content.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.min.js"></script>
<div id="inner-body">
<app></app>
</div>
Vue Components are supposed communicate one-way down to children via Props (where only parents mutate the props on the children, never the children themselves) and children communicate to parents by emitting events. The makes nesting components much easier to reason about, and decouples components properly. So what do you do when the parent wants to change a tab? Let me walk you through a process:
1) Imagine we add a prop called activeTab to the tabs component (I'm not following your code in your question directly here, just basing loosely off it to demonstrate the process easier). The parent will change the value of this prop whenever it wants. The tabs component (aka child component in this case) should not alter the value of the activeTab prop. Instead, inside the tabs component, add a watcher for this prop:
in child component (ie. tabs)
props: {
/**
* Prop parent can use to set active tab
*/
'activeTab': {
type: Number
}
},
watch: {
/**
* Watch for changes on the activeTab prop.
*
* #param {boolean} val The new tab value.
* #param {boolean} oldVal The old tab value.
* #return {void}
*/
activeTab: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
// do something here to make the tabs change
// and never alter the prop itself.
this.switchTab(val)
}
},
2) Now on your parent, you should have a reactive data property that can be named the same as your prop if you want:
in parent component (ie. app)
data: {
activeTab: 0
}
3) Then we need to make it where when you alter the data property above, the prop gets altered too. Here's what it would look like on the tabs component tag:
in parent component (ie. app)
<tabs :activeTab="activeTab" ...
4) What do you do if you want to allow the tabs component to also alter the active tab sometimes? Easy, just emit an event whenever an active tab is changed:
in the child component (ie. tabs)
methods: {
switchTab (newActiveTabValue) {
// store the new active tab in some data property in this component, but not the "activeTab" prop, as mentioned earlier.
this.whatever = newActiveTabValue
// ... now emit the event
this.$emit('switched', newActiveTabValue)
}
5) Your parent should now listen for this emitted event and update its own data property:
in parent component (ie. app)
<tabs :activeTab="activeTab" #switched="activeTab = arguments[0]" ...
Seems a little bit more effort, but it's all worth it as your app grows in complexity and more things become nested. You can read more on Composing Components in the official docs here.