Passing props to deactivated component in Vue - vue.js

I've implemented the Tab feature using "keep-alive", like below, I want to pass "items2" data to the component when the selected currentTabComponent is 'Old', how do i make this work? is there any workaround?
<template>
<div>
<button #click="currentTabComponent = 'New'"> New </button>
<button #click="currentTabComponent = 'Old'"> Old </button>
</div>
<keep-alive>
<component :is="currentTabComponent" :items="currentTabComponent === 'New' ? items : items2"></component>
</keep-alive>
</template>
In the logic, i have,
<script>
export default {
data() {
return {
currentTabComponent: "New",
items:['one','two','three'],
items2:['five','six','seven']
}
}
}
</script>

Even if you use keep-alive props will be passed in the usual way, dynamic or not. So if there is a change in props it should reflect in the subcomponent. keep-alive specifically helps in preserving state changes when the component is not used, and not resetting the state when the component is shown again. But in both cases, props will work fine.
Check the below code:
<div id='component-data'>
<button
v-for="tab in tabs"
v-bind:key="tab"
v-on:click="currentTab = tab">
{{ tab }}
</button>
<keep-alive>
<component v-bind:is="currentTab"
:items="currentTab == 'Bags' ? bags : shirts"
class="tab"></component>
</keep-alive>
</div>
<script>
Vue.component('Bags', {
props: ['items'],
template: "<div>Showing {{ items.toString() }} items in bags.</div>"
});
Vue.component('Shirts', {
props: ['items'],
template: "<div>Showing {{ items.toString() }} items in shirts.</div>"
});
new Vue({
el: "#component-data",
data: {
tabs: ['Bags', 'Shirts'],
currentTab: 'Bags',
bags: ['Bag one', 'Bag two'],
shirts: ['Shirt one', 'Shirt two']
}
});
</script>
You should make sure that the sub-components 'New' and 'Old' have declared the items props in their component definition. Also I hope 'New' and 'Old' are the registered names of the components you are using for tabs.

Related

Vue: All components rerender upon unrelated data property change only if a prop comes from a method that returns an object or array

(Vue 3, options API)
The problem: Components rerender when they shouldn't.
The situation:
Components are called with a prop whose value comes from a method.
The method cannot be replaced with a computed property because we must make operations on the specific item (in a v-for) that will send the value processed for that component.
The method returns an Array. If it returned a primitive such as a String, components wouldn't rerender.
To reproduce: change any parent's data property unrelated to the components (such as showMenu in the example below).
Parent
<template>
<div>
<div id="menu">
<div #click="showMenu = !showMenu">Click Me</div>
<div v-if="showMenu">
Open Console: A change in a property shouldn't rerender child components if they are not within the props. But it does because we call myMethod(chart) within the v-for, and that method returns an array/object.
</div>
</div>
<div v-for="(chart, index) in items" :key="index">
<MyComponent :table="myMethod(chart)" :title="chart.title" />
</div>
</div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent,
},
data: function () {
return {
showMenu: false,
items: [{ value: 1 }, { value: 2 }],
};
},
methods: {
myMethod(item) {
// Remove [brackets] and it doesn't rerender all children
return ['processed' + item.value];
}
}
};
</script>
Child
<template>
<div class="myComponent">
{{ table }}
</div>
</template>
<script>
export default {
props: ['table'],
beforeUpdate() {
console.log('I have been rerendered');
},
};
</script>
<style>
.myComponent {
width: 10em;
height: 4em;
border: solid 2px darkblue;
}
</style>
Here's a Stackblitz that reproduces it https://stackblitz.com/edit/r3gg3v-ocvbkh?file=src/MyComponent.vue
I need components not to rerender. And I don't see why they do.
Thank you!
To avoid this unnecessary rerendering which is the default behavior try to use v-memo directive to rerender the child component unless the items property changes :
<div v-for="(chart, index) in items" :key="index" v-memo="[items]">
<MyComponent :table="myMethod(chart)" :title="chart.title" />
</div>

Teleport in component from slot Vue3

I want to create tabs component for my components library. I want tabs and tab components to work like this:
<b-tabs>
<b-tab
:title="'tab 1'"
:is-active="false"
>
tab content1
</b-tab>
<b-tab
:title="'tab 2'"
:is-active="false"
>
tab content2
</b-tab>
<b-tab
:title="'tab 3'"
:is-active="true"
>
tab content3
</b-tab>
</b-tabs>
So we have two components and they have some props including is-active which by default will be false.
The parent component - tabs.vue will be something like this
<template>
<section :class="mode ? 'tabs--light' : 'tabs--dark'" #change-tab="selectTab(2)">
<div :id="`tabs-top-tabId`" class="tabs__menu"></div>
<slot></slot>
</section>
</template>
here we have wrapper for our single tab which will be displayed here using slot. Here in this "parent" component we are also holding selectedIndex which specify which tab is selected and function to change this value.
setup () {
const tabId = Math.random() // TODO: use uuid;
const data = reactive<{selectedIndex: number}>({
selectedIndex: 0
})
const selectTab = (i: number) => {
data.selectedIndex = i
}
return {
tabId,
...toRefs(data),
selectTab
}
}
TLDR Now as you guys might already noticed in tab.vue I have div with class tabs__menu which I want to teleport some stuff into. As the title props goes into <tab> component which is displayed by the slot in tabs.vue I want to teleport from tab to tabs.
My tab.vue:
<template>
<h1>tab.vue {{ title }}</h1>
<div class="tab" v-bind="$attrs">
<teleport :to="`#tabs-top-tabId`" #click="$emit('changeTab')">
<span style="color: red">{{ title }}</span>
</teleport>
<keep-alive>
<slot v-if="isActive"></slot>
</keep-alive>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
title: {
type: String as PropType<string>,
required: true
},
isActive: {
type: Boolean as PropType<boolean>,
required: true
}
// tabId: {
// type: Number as PropType<number>, // TODO: change to string after changing it to uuid;
// required: true
// }
}
})
</script>
However this span does not get teleported. When I run first snippet for this post I can't see it displayed and I don't see it in DOM.
Why teleported span doesnt display?
I came across this issue recently when using element-plus with vue test utils and Jest.
Not sure if this would help but here is my workaround.
const wrapper = mount(YourComponent, {
global: {
stubs: {
teleport: { template: '<div />' },
},
},
})

Vue.JS - Avoid re-rendering of slots when parent re-renders

I'm struggling on Vue.JS with a component that has as children/slots some "complex" components with canvas data (e.g. Maps).
I want to avoid that when the parent component re-renders, their inner slots re-render. Because of how this components work (or any other scenario), every time it re-renders it needs to do all of it's "loading" steps. Even when saving their real-time state.
For example:
Component.vue
<template>
<div>
<span>{{value}}</span>
<slot></slot>
</div>
</template>
View
<Component v-model="value">
<Map :latitude="0" :longitude="0"/>
</Component>
<script>
this.value = "Hello";
setTimeout(()=>{
this.value="Hello world!";
},1000);
</script>
Is there any way to prevent from slots from re-rendering when their parent re-renders?
Thanks in advance.
Hello use props for a child component, and this child is not will rerender
App
<template>
<div id="app">
<HelloWorld msg="Hello Vue in CodeSandbox!" :somedata="key">
slot information key: {{ key }}
</HelloWorld>
<button #click="key++">key++</button>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld,
},
data() {
return {
key: 0,
};
},
};
</script>
Child
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h3>somedata from props: {{ somedata }}</h3>
<hr />
<slot></slot>
<hr />
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: {
type: String,
default: "",
},
somedata: {
type: Number,
default: 999,
},
},
created() {
console.log("renred");
},
};
</script>
How you can see I used console.log("renred"); in the child component, you can check the console, the information will be shown only one time.
https://codesandbox.io/s/naughty-platform-ib68g?file=/src/components/HelloWorld.vue

how to substitute named slot from parent to child to grandchild with Vuejs

I am faced with a situation to render a third party date picker and also get the value from the third party component. since it is a third party i can't define a prop on it. However, the third party component provides an ability to pass variable in v-model.
So the design of my components requires me to be able to pass named slot from parent to child to grandchild.
for example;
//============ enter-or-upload-task.js =============
Vue.config.productionTip = false;
const sourceoftruth = {orderdate:''};
Vue.component('upload-by-csv',{
template:
`<div><input type="file" /></div>
`
});
//here i want to use the date picker
Vue.component('enter-task-by-form',{
data:function(){
return {
sourceoftruth
};
},
methods:{
getOrderDate: function(){
console.log(sourceoftruth.orderdate);
}
},
template:
`<div>
<label>Order Date:</label>
<!--Hoping to get datepicker component displayed here -->
<slot name="picker"></slot>
<input type="submit" #click.prevent = "getOrderDate()" />
</div>
`
});
const EnterTaskOrUploadByCSV = {
template:
`<div>
<upload-by-csv></upload-by-csv>
<enter-task-by-form>
<!-- wish to pass this named slot to this component -->
<slot name="picker"></slot>
</enter-task-by-form>
</div>
`
}
new Vue({
el:"#app",
data:{
sourceoftruth
},
components:{
'datepicker':vuejsDatepicker,
'enter-form-or-csv':EnterTaskOrUploadByCSV
}
})
// ===========================================================
The index.html is
<script src="./js/lib/vue.js></script>
<script src="./js/lib/vuejs-datepicker.js"></script>
<div id="app">
<enter-form-or-csv>
<datepicker v-model="sourceoftruth.orderdate" slot="picker">
</datepicker>
</enter-form-or-csv>
</div>
<script src = "./js/components/enter-or-upload-task.js"></script>
I have tried passing the named slot to the appropriate position but couldn't get it working. Please I need help on how to solve this.
Glad it's working now. The changes I made that it to work
in EnterTaskOrUploadByCSV i added a template slot to hold the datepicker component going to be received from the parent. then the grandchild component would be expecting the template named slot.
const EnterTaskOrUploadByCSV = {
template:
`<div>
<upload-by-csv></upload-by-csv>
<enter-task-by-form>
<template slot="passpicker">
<slot name="picker"></slot>
</template>
</enter-task-by-form>
</div>
`
}
The grandchild component expecting the template named slot.
Vue.component('enter-task-by-form',{
data:function(){
return {
sourceoftruth
};
},
methods:{
getOrderDate: function(){
console.log(sourceoftruth.orderdate);
}
},
template:
`<div>
<label>Order Date:</label>
<!-- Datepicker will show now -->
<slot name="passpicker"></slot>
<input type="submit" #click.prevent = "getOrderDate()" />
</div>
`
});

How to pass props using slots from parent to child -vuejs

I have a parent component and a child component.
The parent component's template uses a slot so that one or more child components can be contained inside the parent.
The child component contains a prop called 'signal'.
I would like to be able to change data called 'parentVal' in the parent component so that the children's signal prop is updated with the parent's value.
This seems like it should be something simple, but I cannot figure out how to do this using slots:
Here is a running example below:
const MyParent = Vue.component('my-parent', {
template: `<div>
<h3>Parent's Children:</h3>
<slot :signal="parentVal"></slot>
</div>`,
data: function() {
return {
parentVal: 'value of parent'
}
}
});
const MyChild = Vue.component('my-child', {
template: '<h3>Showing child {{signal}}</h3>',
props: ['signal']
});
new Vue({
el: '#app',
components: {
MyParent,
MyChild
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-parent>
<my-child></my-child>
<my-child></my-child>
</my-parent>
</div>
You need to use a scoped slot. You were almost there, I just added the template that creates the scope.
<my-parent>
<template slot-scope="{signal}">
<my-child :signal="signal"></my-child>
<my-child :signal="signal"></my-child>
</template>
</my-parent>
Here is your code updated.
const MyParent = Vue.component('my-parent', {
template: `<div>
<h3>Parent's Children:</h3>
<slot :signal="parentVal"></slot>
</div>`,
data: function() {
return {
parentVal: 'value of parent'
}
}
});
const MyChild = Vue.component('my-child', {
template: '<h3>Showing child {{signal}}</h3>',
props: ['signal']
});
new Vue({
el: '#app',
components: {
MyParent,
MyChild
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-parent>
<template slot-scope="{signal}">
<my-child :signal="signal"></my-child>
<my-child :signal="signal"></my-child>
</template>
</my-parent>
</div>
The release of Vue 2.6 introduces a unified v-slot directive which can be used for normal or scoped slots. In this case, since you're using the default, unnamed slot, the signal property can be accessed via v-slot="{ signal }":
<my-parent>
<template v-slot="{ signal }">
<my-child :signal="signal"></my-child>
<my-child :signal="signal"></my-child>
</template>
</my-parent>
I added this code inside of <v-data-table></v-data-table>
<template
v-for="slot in slots"
v-slot:[`item.${slot}`]="{ item }"
>
<slot
:name="slot"
:item="item"
/>
</template>
And I added a props called slots. When I call the component I send a slots like:
<my-custom-table-component :slots="['name']">
<template v-slot:name="{ item }">
{{ item.first_name + item.last_name}}
</template>
</my-custom-table-component>
You may try this technique.
In this example.
Let assume a parent component wants to share prop foo with value bar.
Parent component
<parent>
<template v-slot:default="slotProps">
// You can access props as object slotObjects {foo"bar"}
<p>{{slotProps.foo}}</p>
</template>
<parent>
Child
<template>
<div class="parent">
<slot :foo="bar" />
</div>
</template>
Got the idea from this video
I hope it helped you accomplish your tasks.