VueJS: Altering prop directly vs. this.[prop] in v-if - vue.js

I built a vue component that get's a number value via prop from the outside laravel blade, like this:
<my-custom-template :mynumber="{{$numbervalue}}" :list:{{$alist}}></my-custom-template>
inside the template I have a v-for list and the prop:
props:{
list:Array,
mynumber: Number,
[..]
}
and
<template>
<ul>
<li v-for="item in list">{{item}}<span v-if="item.id == mynumber">active</span></li>
</ul>
</template>
Whenever the ID of the item is the same as the value mynumber, I want the "active" tag/span to be displayed.
Now in this template I also have a method that sends an axios request and on success it alters the value of the prop "mynumber", so the list should rerender:
axios.post('/api/someurl', this.obj)
.then(res => {
this.mynumber= res.data[something]; // returns a new number from the db.
})
.catch(error => { [..]
};
Issue: If I use this.mynumber in the list's v-if condition, the "active" tag is never being shown. If I use directly == mynumber then it works, but I cannot alter it with the axios response.
How should I approach this correctly?
How can I alter the initial prop, with the new value from the axios call?

First, you shouldn't be modifying props directly, as mentioned in this prior Stack Overflow post.
[Vue warn]: 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: "propRoomSelected"
Second, as seen in the Vue documentation for conditionals, you do not use this within templates, the this is inferred.
Now, to get to the meat of your question, how to look at either a prop or a new value when rendering. Here's how I'd do it.
<template>
<ul>
<li v-for="item in list">{{item}}<span v-if="isCurrent(item.id)">active</span></li>
</ul>
</template>
<script>
export default {
props: ['list', 'myNumber'],
data() {
return {
myNewNumber: undefined
}
},
methods: {
isCurrent(itemId) {
return itemId == (myNewNumber || myNumber)
}
}
}
</script>
Edit:
Note that there is a difference between
return itemId == (myNewNumber || myNumber)
and
return (itemId == myNewNumber) || (itemId == myNumber)
The first one "short circuits" a comparison against myNumber once myNewNumber becomes anything "truthy". Read more here.

Don't mutate props directly. Use this.$emit (Docs: https://v2.vuejs.org/v2/guide/components-custom-events.html) instead to change the myNumber in the parent. myNumber will then automatically update in the child component.

Related

how can i access data property in html template using loop in vue js

i'm trying to use data property commentsToShow in my html template to limit the amount of data that displays on my webpage
this is my template
<div v-if="index < products.length" v-for="(commentIndex, index) in computedProduct">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</div>
if i add commentsToShow in my for loop i get one product but the computed products doesn't work same way the other way round
this my script tag
<script>
export default {
data() {
return {
commentsToShow: 1,
totalComments: 0,
};
},
computed: {
computedProduct() {
let tempRecipes = this.products;
if (this.filterPrice !== "true");
}
};
</script>
if i change computed property to commentsToShow this the error i get in my console
The computed property "commentsToShow" is already defined in data.
please how can i get the value of commentToShow in my template
according to vue's official docs it's not recommended to use v-for and v-if on the same element
try using v-if on a wrapper div or template element
<div v-for="(commentIndex, index) in computedProduct">
<template v-if="index < products.length">
<div class="title pt-4 pb-1">{{products[index].title}}</div>
</template>
</div>
v-if has higher priority so it's executed first and index will not be defined yet.
also you have to return something on your computed property function in order to use it
You can use the slice method on a computed property, like this:
<script>
export default {
data() {
return {
commentsToShow: 1,
allComments: []
};
},
computed: {
listComments() {
return allComments.slice(0, commentsToShow);
}
};
</script>
You can also use pages to show the comments, in this case you can return like this:
return allComments.slice((currentPage - 1) * commentToShow, commentsToShow);
The first argument of slice is the start index, the second is the number of elements to get
The computed property "commentsToShow" is already defined in data.
Equivalently how you cannot have more than one variable with the same name defined in a scope. A computed property cannot have the same name as an existing data property. Essentially, they co-exist in the same namespace, thus they have to be unique.
You have a name clash, and that is what the error is saying.

How to change value of component property in a code in Vue.js?

I have an array of generic child components in my parent component:
<component v-for="item in items" :key="item.id" :is="componentName">
I can get a child via this.$refs, but I can't set a new value for a prop :is like:
this.$refs[id][0].is = 'MyNewComponentName'
How can I set a value of component instance property in a code?
First define your prop structure like
{
...item, // to use your current variables
componentName: 'MyExistingComponentName'
}
Receive the prop and bind it to a data variable, so something like
data: function() {
returns {
items: this.propItem
}
}
Make the required adjustment in your tag
<component v-for="item in items" :key="item.id" :is="item.componentName">
Now you got 2 options, you can either change the item.componentName by referencing this.items in a method, finding the index and changing it or you could get the parent to change the value of the prop using a custom event using $.event(event-name, 'MyNewComponent`). Both methods are fine, it really depends on your requirements.
Refer to https://v2.vuejs.org/v2/guide/components-custom-events.html
You could also read stackoverflow questions on mutating prop values.

Vue: initial triggering of a computed property

In my Vue component, I have a computed property which watches the state and the component, and returns an object.
currentOrganization () {
if (this.$store.state.organizations && this.selectedOrganization) {
return this.$store.state.organizations.filter(org => org.id === this.selectedOrganization)[0];
}
}
Unfortunately, this property does not update automatically when the component loads, though I can see that I have both this.$store.state.organizations and this.selectedOrganization. It does, however, update when I dispatch an action to my Vuex store via the select component below:
<b-form-select id="orgSelect"
v-model="selectedOrganization"
:options="this.$store.state.organizations.map(org => {return {value: org.id, text:org.name}})"
size="sm"
v-on:input="currentOrganizationChange()"
class="w-75 mb-3">
<template slot="first">
<option :value="null" disabled>Change Organization</option>
</template>
</b-form-select>
The component dispatches a Vuex action:
currentOrganizationChange () {
this.$store.dispatch('setCurrentOrganization', this.selectedOrganization);
},
How can I make currentOrganization() compute initially?
The computed will not calculate until you use it.
console.log it, or just write current organization; somewhere in code which executes and it will calculate :)
You should ensure that your computed always returns a value. A linter would have warned you of that error. Chances are your condition is failing -- perhaps this.selectedOrganization is falsy.

Vuejs - - Assigning prop value in child after ajax request in parent.

I am sure I am missing something simple here. I have created a reusable child component that includes a input like the following, and I am assigning the initialValue in the data object from the itemValue prop passed to it from the parent.
<template>
<label>{{itemLabel}}</label>
<input v-model="initialValue" type="text" >
</template>
<script>
export default {
props: ['itemValue'],
data(){
return {
initialValue: this.itemValue,
}
}
</script>
If in the parent component I assign the item-value property directly with a string it works fine.
The problem is I want to set the item-value after making an ajax call in the parent, so I am binding it to a data object property that is set by a method using beforeMount()
<v-child-component :item-value="theValue"></v-child-component>
And...
data(){
return {
theValue: null,
}
},
methods: {
setvalue(){
//make ajax axios get request here then set this.theValue
}
}
beforeMount(){
this.setValue();
}
When I do it this way the it seems the child's item-value is bound to the null value before ajax call completes and sets the actual value. How can I achieve my purpose here?
If you don't want the component to render until theValue is set, use the v-if directive:
<v-child-component v-if="theValue !== null" :item-value="theValue"></v-child-component>

Binding method result to v-model with Vue.js

How do you bind a method result to a v-model with Vue.js?
example :
<someTag v-model="method_name(data_attribute)"></someTag>
I can't make it work for some reason.
Thank you.
Years later, with more experience, I found out that is it easier to bind :value instead of using v-model. Then you can handle the update by catching #change.
Edit (per request):
<input :value="myValue" #change="updateMyValue">
...
methods: {
updateMyValue (event) {
myValue = event.target.value.trim() // Formatting example
}
}
And in a child component:
// ChildComponent.vue
<template>
<button
v-for="i in [1,2,3]">
#click="$emit('change', i) />
</template>
// ParentComponent.vue
<template>
<child-component #change="updateMyValue" />
</template>
<script>
import ChildComponent from './child-component'
export default {
components: {
ChildComponent
},
data () {
return {
myvalue: 0
}
},
methods: {
updateMyValue (newValue) {
this.myvalue = newValue
}
}
}
</script>
v-model expressions must have a get and set function. For most variables this is pretty straight forward but you can also use a computed property to define them yourself like so:
data:function(){
return { value: 5 }
},
computed: {
doubleValue: {
get(){
//this function will determine what is displayed in the input
return this.value*2;
},
set(newVal){
//this function will run whenever the input changes
this.value = newVal/2;
}
}
}
Then you can use <input v-model="doubleValue"></input>
if you just want the tag to display a method result, use <tag>{{method_name(data_attribute)}}</tag>
Agree with the :value and #change combination greenymaster.
Even when we split the computed property in get/set, which is help, it seems very complicated to make it work if you require a parameter when you call for get().
My example is a medium sized dynamic object list, that populates a complex list of inputs, so:
I can't put a watch easily on a child element, unless I watch the entire parent list with deep, but it would require more complex function to determine which of the innter props and/or lists changed and do what fromthere
I can't use directly a method with v-model, since, it works for providing a 'get(param)' method (so to speak), but it does not have a 'set()' one
And the splitting of a computed property, have the same problem but inverse, having a 'set()' but not a 'get(param)'