Vuejs Updating v-for by reference - vue.js

So I have a simple v-for and each item in the v-for has a #click on it
<result-index>
<li v-for="(result, index) in results" #click="getReult(results[index])">
{{ result.name }}
</li>
</result-index>
Now, my method for getResult just assigns that result to a result data:
methods: {
getResult: function(result) {
// when the child <result-update> updates this, it updates fine, but it doesn't update the v-for reference of this.
this.result = result;
}
}
Now I have another component that get the data for that result and display it:
<result-index>
<li v-for="(result, index) in results" #click="getReult(results[index])">
{{ result.name }}
</li>
<result-update v-if="result" v-model="result">
//... here is a form to access the result and update it
</result-update>
</result-index>
In my result-update I am updating via the index and value like so:
methods: {
update(e) {
this.$emit("input", //data here...);
},
}
watch: {
value: function() {
this.form = this.value;
},
},
created() {
this.form = __.cloneDeep(this.value);
}
Which update the parent result fine (the one we used the #click on), but not the v-for reference of that result, so how can I update the v-for reference of the result when it changes in , also please note, it is not possible for me to put the inside the v-for due to the css design of this, it needs to be seperate from the ...

When this.result = result, this.result points to one address of the memory.
When <result-update v-if="result" v-model="result"> receives input event then assign new value to this.result, it will make this.result = newValue (actually point to another address of the memory for newValue), so it will not change the value for result[index] as you expected.
Check below demo:
const test = {result: []}
let value1 = ['a']
console.log('org', test)
test.result = value1 // assign with new array
console.log('Replace the whole array', test)
value1[0] = 'b' // assign new value to first element of value1
console.log('Update one item of the array', test) //test.result and value1 point to same address of the memory
The solution:
You can save the index for current <result-index>, then change the value by this.results[index].
So adjust your codes to below then should work fine.
For the template of component <result-index>, change it to:
<result-index>
<li v-for="(result, index) in results" #click="getReult(index)">
{{ result.name }}
</li>
</result-index>
For the method=getResult inside component <result-index>, change it to:
methods: {
getResult: function(index) {
this.selected = index;
}
}
Inside the parent component, change the template to:
<result-update v-if="selected >= 0" v-model="results[selected]">
//... here is a form to access the result and update it
</result-update>

Related

vue.js watcher value does not update value in select v-model

I have a form with a two select boxes.sb1 and sb2. sb2 with a v-model: result.
If I change the value of the first one (sb1) I have to do a some async operation with the value and to obtain a new value that must be placed in the other select box(sb2). For this I've used a watch executing an async fuction, and after I obtain the async value I make this.result = newValue.
In the console.log is ok, but not in the select box (sb2) that shows nothing
but then, when I unselect a value in the first box (sb1) the value I previously obtained shows up in (sb2)
in general, what is going on here? How can I workaround this?
Thanks in advance
EDIT:
<label for="timezone" >Timezone</label>
<select
id="timezone"
v-model="localValue.timezone"
>
<option v-for="(timeZone, index) in arrayTZ()" :key="index" :value="timeZone" :selected="localValue.timezone === timeZone">
{{ timeZone }}
</option>
</select>
and the watcher
methods:{
async getFunction1(id){
await dispatchingActions
return objectFromgetFunction1.current
}
},
watch: {
async "localValue.ManagerId"(id){
console.log(id)
if(id !== null){
await this.getFunction1(id)
this.localValue.timezone = objectFromgetFunction1.current.timezone;
}
if(id === null){
this.localValue.timezone = null
}
console.log(this.localValue.timezone)
},

Changes made to a computed array won't show after a prop binding

I'm quite new to vue and right now I'm trying to figure out how to make changes to a computed array and make an element react to this change. When I click the div element (code section 4), I want the div's background color to change. Below is my failed code.
Code section 1: This is my computed array.
computed: {
arrayMake() {
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
Code section 2: This is where I send it as a prop to another component.
<test-block v-for="(obj, index) in arrayMake" v-bind:obj="obj" v-on:act="act(obj)"></card-block>
Code section 3: This is a method in the same component as code section 1 and 2.
methods: {
act(obj){
obj.check = true;
}
Code section 4: Another component that uses the three sections above.
props: ["obj"],
template: /*html*/`
<div v-on:click="$emit('act')">
<div v-bind:style="{ backgroundColor: obj.check? 'red': 'blue' }">
</div>
</div>
Easiest way to achieve this, store the object into another data prop in the child component.
child component
data() => {
newObjectContainer: null
},
onMounted(){
this.newObjectContainer = this.obj
},
methods: {
act(){
// you don't need to take any param. because you are not using it.
newObjectContainer.check = !newObjectContainer.check
}
}
watch: {
obj(val){
// updated if there is any changes
newObjectContainer = val
}
}
And if you really want to update the parent component's computed data. then don't use the computed, use the reactive data prop.
child component:
this time you don't need watcher in the child. you directly emit the object from the method
methods: {
act(){
newObjectContainer.check = !newObjectContainer.check
this.emits("update:modelValue", nextObjectContainer)
}
}
parent component:
data() => {
yourDynamicData: [],
},
onMounted(){
this.yourDynamicData = setAnArray()
},
methods(){
setAnArray(){
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
}
}
okay above you created a reactive data property. Now you need the update it if there is a change in the child component,
in the parent first you need object prop so, you can update that.
<test-block v-for="(obj, index) in arrayMake" v-model="updatedObject" :obj="obj"></card-block>
data() => {
yourDynamicData: [],
updatedObject: {}
},
watch:{
updatedObject(val){
const idx = val.index
yourDynamicData[idx] = val
}
}

How to maintain original array sort order with Vue.js

I'm using v-for with Vue.js (v2.6.12) to display entries in an object e.g.
{
"12345": {
name: "foo",
isAccepted: true,
},
"56789": {
name: "bar",
isAccepted: false,
}
}
HTML:
<div v-for="item in sortMyItems(items)" v-bind:key="item.id">
<span>{{ item.name }}</span>
<span>{{ item.isAccepted }}</span>
</div>
Sort method in VM:
methods: {
sortMyItems: function(items) {
var accepted = [];
var rejected = [];
for (var id in items) {
var item = items[id];
if (item.isAccepted) {
accepted.push(item);
} else {
rejected.push(item);
}
}
return accepted.concat(rejected);
}
}
It's important to me to maintain the object structure of items in the model, which is why I'm doing it this way. The problem I have is that when the isAccepted property of any of the items in my data structure change, Vue re-renders the items that the sort order reflects the new ordering. I understand that this is a very useful feature of Vue, but in my case I really don't want this to happen. I want the sort order to be maintained the way it is after sortMyItems is first called. Is there a way to tell Vue to not monitor changes or just not re-render e.g. v-once
As far as I understood your question:
You could call sortMyItems(items) in the created Lifecycle Hook and store the result in a property of data.
Then, you can iterate over that property in your v-for:
export default {
data() {
return {
sortedData: [];
}
},
created() {
this.sortedData = sortMyItems(items)
}
}

how to append multiple query parameters in url - NuxtJS

I am creating a nuxt ecommerce application. I have a situation where I have more than 10,000 items in a category and i want to create related filters for the products.
My question is how do i append url (add & remove query parameters) so that i can filter products.
I have tried something like this by adding a change event to !
<ul>
<li>
<b-form-checkbox #change="filterProduct">
<label class="ui__label_checkbox">Apple</label>
</b-form-checkbox>
</li>
<li >
<b-form-checkbox #change="filterProduct">
<label class="ui__label_checkbox">Mango</label>
</b-form-checkbox>
</li>
</ul>
methods:{
filterProduct() {
this.$router.push({ query: Object.assign({}, this.$route.query, { random: "query" }) });
},
}
This approach does append the url only once but removes the checked state of the checkbox which i don't want
I want similar to below everytime i click checkbox, it must retain the state of the checkbox at the same time append to the url
www.foobar.com/?first=1&second=12&third=5
Here's what you should do. First of all, you should all your filters state in data()
data() {
return {
filter: {
first: this.$route.query.first || null,
second: this.$route.query.second || null,
third: this.$route.query.third || null
}
}
}
Then you set up a watcher that fires when any filter changes, obviusly you need to v-model the inputs in your <template> to the fields in data()
watch() {
filter: {
handler(newFilters) {
const q = complexToQueryString({
...this.filter,
})
const path = `${this.$route.path}?${q}`
this.$router.push(path)
}
}
}
The complexToQueryString function is a thing of mine which removes null values from the query and also works for filters that are arrays. I did this because my API reads null as String 'null'.
const complexToQueryString = (object, parentNode = null) => {
const query = Object.entries(object).map((item) => {
const key = parentNode ? `${parentNode}[${item[0]}]` : item[0]
const value = item[1]
if (Array.isArray(value)) {
return arrayToQueryString(value, key)
} else if (value instanceof Object) {
return complexToQueryString(value, key)
} else if (item[1] !== undefined) {
return [
Array.isArray(item[0]) ? `${key}[]` : key,
encodeURIComponent(item[1]),
].join('=')
}
return ''
})
.filter(empty => empty)
.join('&')
return query
}
Now it should work, if you change the filter value then the data.filter.first changes the value, which fires the watcher, which updates the URL.
The best thing about this aproach is that now you can copy & paste the URL and the filter is exactly the same and returns the same result.
Your approach is almost correct, except that on page request, router-level You should append all the query parameters to route params.
Then asign those params to data inside Your filter page, and mutate them, also updating the query like You're doing now. This way You'll have query updated, and checkboxes wont lose state as they will depend on data, rather than on params.
routes: [{
path: '/path',
component: Component,
props: (route) => ({
filter1: route.query.filter1,
filter2: route.query.filter2,
filter3: route.query.filter3
})
}]

vuejs2 passing data between parent-child is wiping childs value

In VueJS 2 I am trying to create a component that gets and passes data back to the parent which then passes it to another component to display.
The component that gets the data has a user input field it uses to search. When I have it pass data back to the parent using $emit the value in the input keeps being wiped.
I am receiving the below mutation error but I haven't directly tried to change the userSearch field in the component so I am not sure why.
"Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "userSearch" (found in PersonField)"
Relevant html
<person-field v-on:event_child="eventChild"></person-field>
<person-search :prop="personListArray" ></person-search>
Parent app
var app = new Vue({
el: '#app',
data: {
personListArray : [],
tempArray: []
},
methods: {
eventChild: function (arr) {
this.personListArray = arr
}
}
})
Component 1, displays a user input. Uses the input to search and bring back data. Starts search when the length of the input is more then 2. As soon as you hit the 3rd character something is causing the input to clear which I don't want.
Vue.component('person-field', {
props: ['userSearch'],
template: '<input class="form-control" v-model="userSearch" >',
watch: {
userSearch: function () {
var arr = []
if (typeof this.userSearch !== 'undefined') { //added this because once i passed 3 characters in the field the userSearch variable becomes undefined
if (this.userSearch.length > 2) {
$.each(this.getUsers(this.userSearch), function (index, value) {
var obj = {
Title: value.Title,
ID: value.ID
}
arr.push(obj)
});
this.$emit('event_child', arr) //emits the array back to parent "eventChild" method
} else {
console.log('no length')
}
} else {
console.log('cant find field')
}
},
},
methods: {
getUsers: function (filter) {
//gets and returns an array using the filter as a search
return arr
},
}
});
Component 2 - based on the personListArray which is passed as a prop, displays the results as a list (this works)
Vue.component('person-search', {
props: ['prop'],
template: '<ul id="personList">' +
'<personli :ID="person.ID" v-for="person in persons">' +
'<a class="" href="#" v-on:click="fieldManagerTest(person.Title, person.ID)">{{person.Title}}</a>' +
'</personli></ul>',
computed: {
persons: function () {
return this.prop
}
},
methods: {
fieldManagerTest: function (title, ID) { //Remove item from users cart triggered via click of remove item button
//var user = ID + ';#' + title
//this.internalValue = true
//this.$emit('fieldManagerTest');
//this.$parent.$options.methods.selectManager(user)
},
},
});
Component 3, part of component 2
Vue.component('personli', {
props: ['ID'],
template: '<transition name="fade"><li class="moving-item" id="ID"><slot></slot></li></transition>'
})
;
The reason you get the warning,
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"userSearch" (found in PersonField)
Is because of this line
<input class="form-control" v-model="userSearch" >
v-model will attempt to change the value of the expression you've told it to, which in this case is userSearch, which is a property.
Instead, you might copy userSearch into a local variable.
Vue.component('person-field', {
props: ['userSearch'],
data(){
return {
searchValue: this.userSearch
}
},
template: '<input class="form-control" v-model="searchValue" >',
...
})
And modify your watch to use searchValue.
Here is an example.