Vue 3 migrate broken scope by key - vue.js

I migrate v-for from vue 2 to vue 3 and get different behavior.
Main component render array of two items to child component with scope KEY:
let items = computed(() => [ { subarray:[] }, { subarray:[] } ]);
<div v-for="(item, i) in items" :key="i">
<X :item="item"></X>
</div>
Component "X":
<div v-for="i in item.subarray">
{{ i }}
</div>
const props = defineProps({
item: Object,
});
function add(){
props.item.subarray.push("abc");
}
But then i add new item to subarray in SECOND instance of component 'X', item ADDED TO FIRST instance of component X.
What is problem ?
This code in vue 2 work fine, then i add item in second instance of component X i get right show in second instance.

Related

Vue 3: access VueComponent object placed in slots

I'm working on tab component and I want to render tab labels in parent component by getting child's slot, named 'label'
In Vue 2.x I could approach that, by referring to $slots property of tab component, in Tabs.vue:
<template>
<section class="tabs">
<ul class="tabs-labels">
<li
v-for="tab in tabs"
:key="tab._uid"
:class="[{'active': tab.isActive}, 'tab-label']"
#click="selectTab(tab);"
>
{{ tab.$slots.label }}
</li>
</ul>
<div class="tabs-content">
<slot/>
</div>
</section>
</template>
<script>
export default {
name: 'Tabs',
data () {
return {
tabs: [],
};
},
mounted () {
// filter tabs in case there were additional vue components placed in slots
this.tabs = this.$children.filter(tab => tab.$options.name === 'TabContent');
},
methods: {
selectTab (selectedTab) {
// set isActive property of the tab by comparing their uids
this.tabs.forEach(tab => {
tab.isActive = (tab._uid === selectedTab._uid);
});
},
},
};
</script>
TabsContent.vue:
<template>
<div v-show="isActive" class="single-tab-content">
<slot/>
</div>
</template>
<script>
export default {
name: 'TabContent',
data () {
return {
isActive: false
};
},
};
</script>
Here, when the tab label clicked, in Tabs.vue I iterate through tabs array and setting their isActive property, comparing their uid and uid of selectedTab
But in Vue 3.x API of slots has changed, so I changed the way of getting tab contents:
from
this.tabs = this.$children.filter(tab => tab.$options.name === 'TabContent');
to
this.tabs = this.$slots.default().filter(tab => tab.type.name === 'TabContent');
but as I understand, it getting only vNodes, not actual VueComponent that rendered, so when I'm executing selectTab method
tab.isActive = (tab._uid === selectedTab._uid);
it updates only isActive properties for tabs, that were saved in tabs array, not for actual tab contents, so v-show never changes.
Is there any way to get actual rendered VueComponents from <slots>? Or maybe this approach is wrong from the beginning and I should try something else?
Edit
CodeSandboxes for both versions:
Vue 2.x -ignore the error about refering to children during render, it's a bug on CodeSandbox
Vue 3.x
Its a bit more complicated with Vue 3. You will want to look into using provide and inject. here is a good example.
https://gist.github.com/cathrinevaage/4eed410b31826ce390153d6834909436
sandbox - https://codesandbox.io/s/happy-rubin-z414h?file=/src/App.vue
The example above is using typescript however you get the idea.

Pass value from v-for input components with ref to parent in Vue3

I've been struggling a wile with this and would apreaciate some assistance.
I have a Vue3 child component that contains two input fields.
Child
<template>
<label>
<span>asset value</span>
<input type="number" :value="value" />
</label>
<label>
<span>target percentage</span>
<input type="number" :value="targetPercentage" />
</label>
</template>
There is a button in my main component to add this component unlimited times to the template.
I am trining to use the Composition API to get the values from those input fields via ref.
Parent
setup() {
// List of predefined assets
const assetList = reactive([
{
title: 'Position 1',
value: '',
targetPercentage: '',
}
]);
const assetsRefs = ref([]);
// Make sure to reset the refs before each update.
onBeforeUpdate (() => {
assetsRefs.value = [];
})
onUpdated (() => {
console.log(assetsRefs)
})
return {
assetList,
assetsRefs,
};
}
In the template of my main component I render those assets like the following
Parent
<template>
<div>
<Asset v-for="(asset, index) in assetList"
:key="index"
:value="asset.value"
:targetPercentage="asset.targetPercentage"
:ref="element => { assetsRefs[index] = element }"
/>
<button #click="addAsset">Add New Position</button>
<button #click="onSubmit">Calculate</button>
</div>
</template>
What I am trying to achive is, that there is some sort of two way binding between the parent and the child components. I obviously can't use v-model in my child components, because I already use props to bind the predefined values from the assetList (first code block).
I already found good examples for todo lists with refs and v-for or for submitting forms so that I could render multiple assets. But I never got some input field rendering with v-for and refs to actually get the input values.

Share data between two child components Vue js

i am trying to send and render some data from a child component to another child component & both components are rendered using one main component , how can i pass the data between two child components (in my vue app)?
exg
I have two child components A & B , "B" has click add to cart click event and have data of cart items , & i have template for cart item in component "A"
In this situation, as both components share the same parent, it's common to move whatever shared state you need into the parent component and pass to the children as props.
Where components don't shared a direct parent, you can use an event bus (for very small apps) or Vuex. Vuex lets you share state between components in your app without having to pass props down through multiple levels.
There are a lot of ways to achieve this. I am explaining with global even bus concept. Following is an example. Here, child A and child B are communicating through event bus
const eventBus = new Vue ()
Vue.component('ChildB',{
template:`
<div id="child-b">
<h2>Child B</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
</div>`,
data() {
return {
score: 0
}
},
created () {
eventBus.$on('updatingScore', this.updateScore) // 3.Listening
},
methods: {
reRender() {
this.$forceUpdate()
},
updateScore(newValue) {
this.score = newValue
}
}
})
Vue.component('ChildA',{
template:`
<div id="child-a">
<h2>Child A</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
<button #click="changeScore">Change Score</button>
<span>Score: {{ score }}</span>
</div>`,
props: ["score"],
methods: {
changeScore() {
this.score +=200;
eventBus.$emit('updatingScore', this.score+ 200)
}
}
})
Vue.component('ParentA',{
template:`
<div id="parent-a">
<h2>Parent A</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
<child-a :score="score"/>
<child-b/>
</div>`,
data() {
return {
score: 100
}
}
})
Vue.component('GrandParent',{
template:`
<div id="grandparent">
<h2>Grand Parent</h2>
<pre>data {{ this.$data }}</pre>
<hr/>
<parent-a/>
</div>`,
})
new Vue ({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<grand-parent/>
</div>
Ideally, you should use Vuex as state management pattern.
But if your application is very simple and you don't need to do those operations often, you can pass data in emit payload from child to parent, and then parent component should pass it to another child via props

Strange issue with Vue2 child component object v-for list render

I have a parent component that contains an array and an object:
data() {
return {
products: [],
attributes: {},
}
},
When my parent component is loaded, it gets some AJAX data and populates the variables:
mounted() {
axios.get('/requests/getProducts/' + this.currentCategory).then(response => {
this.products = response.data;
this.createAttributes(); // runs some methods to populate the attributes object
});
},
I then have a child component that I will use to display the attributes. Code from parent template:
<search-attributes :attributes="attributes"></search-attributes>
I have the props declared in my child component:
props: ['attributes'],
So far so good, I can console log the data from parent and child...
Problem is when I try to v-for the attributes in the child component, it simply returns nothing:
/////////////// this does not render anything ///////////////
<template>
<div>
<div v-for="(value, key) in attributes">
{{ key }}
</div>
</div>
</template>
However, if I pass both the products and attributes variables to the child component, and render products in the child component, attributes will start working!
/////////////// this works /////////////
<template>
<div>
<div v-for="(value, key) in attributes">
{{ key }}
</div>
{{ products }}
</div>
</template>
What the heck is going on?
Do you need to see my parent methods?
I expect you are running into a change detection caveat. Vue cannot detect when you add properties dynamically to an object. In this case, you begin with an empty attributes object. If you add properties to it without using $set, then Vue does not understand a change has occurred and will not update the DOM.
Update the createAttributes to use $set.
The reason it works when you pass products is Vue can detect the change you make (this.products = response.data), so it renders the DOM, which shows the latest attributes as a by product.

How split vue single components template section in to smaller subtemplates

My application is being build on vuejs#2 has multiple forms most of the share same html template with add and reset button. As well as same method, resetForm nullifies the "item" property and resets the form, and create method sends the item to the backend.
<div class="row">
<div class="action">
<button class="btn btn-white" #click="create()">✎ Add</button>
<button class="btn btn-white" #click="resetForm()">❌ Reset</button>
</div>
</div>
I can share methods via mixins with each component but I can't share "template partial" same way. How to you approach such scenario?
I tried to create component create-reset-buttons, but I have no way to trigger parent method as each component encapsulates its functionality and does not allow to modify props from the child. Which need to be done in order to reset the parent form.
Components are not allowed to modify the props, but there are ways child can communicate to parent as explained here in detail.
In Vue.js, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.
How to pass props
Following is the code to pass props to chile element:
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
How to emit event
HTML:
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
JS:
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})