How to update Vue array of components with a click IN one of the children components - vue.js

I am displaying an array of child components and in the child component, I need to click some text to make THIS child push to the front of the array.
I've tried it where the child sees the array which doesn't seem to be of any help. And now I've tried to emit with an event to the parent component and I still can't seem to figure out how to push any of the child components to the beginning of the loop with a click IN the child component.
Parent component and method:
<location-item v-for="(location, index) in results"
:key="location.Id"
:index="index"
:location="location"
#changeLocation="changeLocation(location, $event)" />
methods(): {
changeLocation(location, newIndex) {
location.index = newIndex;
},
}
Child components's clickable text and method
<a #click="changeCurrent">
Change Location
</a>
methods: {
changeCurrent() {
this.$emit('changeLocation', this.newIndex);
}
},
So when it's clicked in the child component, it throws THAT child component to the beginning of the v-for loop. The suggestion I'm giving is the closest I feel I've gotten, yet, it does nothing. I haven't seen anyone online really seem to do this so I figured I'd come to ask here.

This is very much a JavaScript question. You are on the right track with the emit from child to parent, need to do some javascript using splice().
As you are already passing the index of the child component, to the component, that is the only thing we need to pass to parent, so...
Child:
props: {
location: String,
index: Number,
},
methods: {
changeCurrent() { // pass the childs index to parent
this.$emit("change-location", this.index);
},
},
Then in parent changeLocation function we use the index to reorder array:
changeLocation(index) {
let item = this.results.splice(index, 1); // remove item from array
this.results.splice(0, 0, item[0]); // put item first in array
},
Here is a DEMO for you. You can also use unshift to add the item.

Related

Why aren't there props for child -> parent and $emits for parent -> child?

I have a page with a component and the page needs to access a variable in that component. Would be nice if it were reactive. Then from the page I need to activate a function in the component. Would be nice if it could be done without a reactive variable. My question is 1: what's the best way to activate the function from the parent, for example when I click a button and 2: it seems very unintuitive and random to me that they aren't both possible in both directions? Anyone maybe know how Vue suggest you do it? This whole thing seems so complex relative to the relatively simple thing I'm trying to do.
I guess I try to use props? Or are refs a better option here?
So in general: you use refs, if you need the dom element, that's the whole purpose of refs. Since you don't mention that you n ed the dom element, you don't need to use that here.
There are 3 ways of communication: parent to child via props: https://vuejs.org/guide/components/props.html
child to parent via events
https://vuejs.org/guide/components/events.html
and anyone to anyone via event bus, which need an extra lib in vue3 and is out of scope for your question
https://v3-migration.vuejs.org/breaking-changes/events-api.html#event-bus
If you want to execute a function in the component whenever the value changes, you can put a watcher on the prop.
The other way around, from child to parent, you just create a listener to your emitted event and invoke a function of your choice. There are good examples in the docs in my opinion.
As per my understanding, You want to trigger the child component method from the Parent component without passing any prop as a input parameter and in same way you want to access child component data in the parent component without $emit. If Yes, You can simply achieve this using $refs.
You can attach the ref to the child component and then access it's variables and methods with the help of this $refs.
Live Demo (Just for a demo purpose I am using Vue 2, You can make the changes as per Vue 3) :
Vue.component('child', {
data: {
childVar: ''
},
methods: {
triggerEventInChildFromParent() {
console.log('Child Function triggered from Parent');
}
},
mounted() {
this.childVar = 'Child component variable'
}
});
var app = new Vue({
el: '#app',
methods: {
triggerEventInChild() {
this.$refs.child.triggerEventInChildFromParent()
}
},
mounted() {
console.log(this.$refs.child.childVar)
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="triggerEventInChild">
Button in Parent
</button>
<child ref="child"></child>
</div>

VueJs - What's The Correct Way to Create a Child Component With Input Fields

I'm trying to use vuejs to display a list of instances of a child component.
The child component has input fields that a user will fill in.
The parent will retrieve the array of data to fill in the child components (If any exists), but since they're input fields the child component will be making changes to the value (Which generates errors in the console, as child components aren't supposed to change values passed from the parent).
I could just be lazy and just do everything at the parent level (i.e. use a v-for over the retrieved array and construct the list of elements and inputs directly in the parent and not use a child component at all), but I understand that it's not really the vuejs way.
I'm not very familiar with child components, but I think if it was just static data I could just declare props in the child component, and fill it from the parent.
However what I kind to need to do is fill the child component from the parent, but also allow changes from within the child component.
Could someone please describe the correct way to achieve this?
Thanks
You can use inputs on child components. The pattern is like this (edit it's the same pattern for an array of strings or an array of objects that each have a string property as shown here):
data: function() {
return {
objects: [ { someString: '' }, { someString: '' } ]
}
}
<the-child-component v-for="(object, i) in objects" :key="i"
v-model="object.someString"
></the-child-component>
Then, in the child component:
<template>
<div>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
/>
</div>
</template>
export default {
name: 'the-child-component',
props: ['value'],
}
See it described here: https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components

how to get the right object of an array in the parent

In my app I have a parent component which queries an api to fill an array of objects.
These objects with different properties are then passed to multiple child components with a v-for loop.
In a child component I can edit certain properties of the object via a form with textinputs.
Using #change I emit this to the parent, so I can edit the object there and post it to the api.
However, how do I find the right object in the array?
I could loop the array in the parent to find the right object based on the id (one of the properties), but that seems very slow to me. Is there a way to directly get the right object? Or do I make the api call for updates in the child component? That seems not logical, because the parent won't know the object changed?
Within the v-for loop you have access to the elements index within the array
<div v-for="(item, i) in items" #change="update(item, i)"></div>
<script>
export default {
data() {
return {
myArray: [{}, {}]
}
},
methods: {
update(item, i) {
this.myArray[i] = item
}
}
}
</script>
https://jsfiddle.net/rfxtpua1/

Can't copy props to model data and render it in Vue 2

I'm having this problem that looks a lot like a bug to me and I can't figure out how to solve it.
I created a generic list component and I tell it what child component it should insert in each item and what are the data it should pass to the child component. I'm passing everything as props along with the list (array) itself.
The problem is that I can't mutate the list props. So I try to copy it to model attribute. Otherwise I get this error:
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders.....
And I can't just make it work in any of the lifecycle events. When I save the file and the hot-reloading reloads the page, the list is there, rendered, full of items. When I press F5 to manually reload the page, it is no more. Everything seems to be alright with code though
So in the parent component I'm doing this:
<List ref="link_list"
:list="this.foo.links" //this is array
:child="'LinkFormItem'" //this is the name of the child component
:section_name="'Links'"
:defaults="{content: '', type: 'facebook'}" />
In the List component I get this:
Template
<li class="" v-for="item in datalist">
<component :is="child" :item="item" ></component>
<button v-on:click='remove(index++)' type="button" name="button" class='red button postfix small'>Remove</button>
</li>
Script
<script>
import Child1 from './Child1'
import Child2 from './Child2'
export default {
name: 'search',
props: ['child', 'list', 'defaults','section_name'], //it is received as 'list'
components: {
Child1, Child2
},
data () {
return {
index: 0,
datalist: [] //i'm trying to copy 'list' to 'datalist'
}
},
beforeMount: function () {
// i'm copying it
for(var k in this.list){
this.datalist.push(this.list[k])
}
},
methods: {
//and here I should change it the way I want
add: function () {
this.datalist.push(this.defaults)
},
getList () {
return this.datalist;
},
remove(index){
var datalist = [];
for(var k in this.datalist){
if(k != index) datalist.push(this.datalist[k]);
}
this.datalist = datalist;
}
}
}
</script>
I don't see any problems with my Script. What is going on??
#edit
Ok, some console.log later I found out what the problem seems to be. The HTTP Request is really taking much longer than the mounting of the component to happen. But when it happens, it is not triggering the update in the list component. Nothing is re-rendered and the list is empty.
Workaround
well I realised the problem was related to propagation. I made a few changes in the code to asure the parent component was updating and changing the model value. but the child component (the list component) was not receiving it.
then I gave up trying to understand why and did the following:
1- used the ref in the child component to force an update in the child component with $forceUpdate and then I was assigning the props to the model in the beforeUpdate event. It was causing an error: an re-rendering loop. The update caused a new update and so on. We could just use a flag to stop it.
2- Instead I just called a child method directly:
this.$refs.link_list.updateList(data.links);
I hate this approach because I think it's way too explicit. But it did the job. Then in the child component a new method:
updateList(list){
this.datalist = list;
}
3- The other possibility that passed through my mind was emitting an event. But I didn't try, too complicated
You can simply do like as follows
data () {
return {
index: 0,
datalist: this.list // to copy props to internal component data
}
},
Once you done about you need to apply data manipulation opertions on new this.datalist , not on this.list
If you don't want to mutate the original list array you can do this:
data () {
return {
index: 0,
datalist: Object.assign({}, this.list)
}
}
I think this will help you

How can I get a child component's watcher's updated value from parent component in Vue 2?

I have two components that are in a parent-child relationship. In the child select component I've defined a watcher for the currently selected value and I want the parent to get the updated value whenever it changes. I tried to use events but the parent can only listen to its own vm.
In the child component
watch: {
selected (value) {
this.$emit('selectedYearChange', value)
}
}
In the parent
mounted () {
this.$on('selectedYearChange', function (value) {
this.selectedYear = value
})
}
This obviously doesn't work because of the reason I mentioned above.
I wasn't able to find relevant examples in the documentation.
You can setup an event listener on the child component element itself where its used in the parent and pass it a method to be executed
//parent component
<div id="app>
<child-comp #selected-year-change='changeYear'></child-comp>
</div>
In your parent component use the method changeYear to change the value of `selectedYear'
methods:{
//you get the event as the parameter
changeYear(value){
this.selectedYear = value
}
}
See the fiddle
And avoid using camelCase for event names use kebab-case instead as HTML attributes are case-insensitive and may cause problems
html:
<Parent>
<Child #selected-year-change=HandleChange(data)></Child>
</Parent>
Javascript/Child Component:
watch: {
selected (value) {
this.$emit('selectedYearChange', value)
}
}
Javascript/Parent Component:
HandleChange: function(data){}
good luck. maybe work!