Vue JS Using local variable without defining in data() function - vue.js

I am a beginner to Vue JS. I have to use a variable inside a component whose value changes often.
So when I declare and define it under data() the following warn is coming in Chrome console
Since when there is a change in data() variables automatically Vue framework calls render function.
Is there any way to declare and use a variable other than declaring it in data() method ??
<template>
<ul>
<div v-for="(list,index) in itemlist" :key="index">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"}],
firstChar:"$"
}
},
methods : {
isFirstCharSame: function(str) {
if(str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
}
}
}
</script>
Expected output should be like this
Inside Group A It should display all the elements starting with A

Below we will render using a computed property to make sure its sorted alphabetically and then render your first char. Though You should be using grouping imo.
<template>
<ul>
<div v-for="(list, index) in sortedlist" :key="`people_${index}`">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"},
],
firstChar: '',
};
},
methods: {
isFirstCharSame(char) {
if (str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
},
},
computed: {
sortedList() {
return this.itemList.sort((a, b) => {
if (a.label > b.label) {
return 1;
}
if (b.label > a.label) {
return -1;
}
return 0;
});
},
},
};
</script>
And yes, You can update your data any time you wish and the component will do a re render to reflect it.

You can declare variables in your component within your methods or inside computed properties, etc., but they won't be reachable from the template or the rest of the code nor they would be reactive.
The only way for them to be reactive and reachable from the higher scope is adding the data property to the component in the following way:
data: function () {
return {
foo: 'bar'
}
},
or
data () {
return {
foo: 'bar'
}
},
Besides this, the reason of your error is that you are mutating the state of your variables inside the render. When this happens, Vue re-renders the template because the values have mutated and calls again to the function and voilĂ : there you have an infinite loop.
You should probably check the function you are calling and try to replace the changing variables from the data property with local variables that take their data from the actual data variables.

Related

Vue warn]: You may have an infinite update loop in a component render function

Getting an infinite loop error when looping through <a> attributes in vue.js.
I have a method that loops through and adds attributes dynamically but when I use the method by binding it to an attribute in the <a> I get the error from the title. The attributes are a nested object within the original products array of objects.
Vue Code
<template>
<div>
<p>
<a
v-for="product in products"
:href="product.product_url"
type="submit"
v-bind:additionalAttrs="addAttributes()"
>
Click Me
</a>
</p>
</div>
</template>
<script>
export default {
data () {
return {
addedAttributes: [],
};
},
props: {
products: Array,
},
methods: {
addAttributes() {
this.products.forEach(product => {
for (const [key, value] of Object.entries(product.attributes)) {
this.addedAttributes.push(`${key}: ${value}`);
}
});
}
}
}
</script>
You use a method call to pass its result to additionalAttrs prop but it's not reactive and potentially can be called as many times as you have elements in products array
You just need one computed prop instead of an array and a method because they simply depend on products prop:
<a
v-for="product in products"
:href="product.product_url"
type="submit"
v-bind:additionalAttrs="addedAttributes"
>
Click Me
</a>
computed: {
addedAttributes() {
const addedAttributes = []
this.products.forEach(product => {
for (const [key, value] of Object.entries(product.attributes)) {
addedAttributes.push(`${key}: ${value}`);
}
});
return addedAttributes
}
}

How do you use buefy's b-taginput in a custom component so that it works like v-model, It is only working one way binding?

I'm new to vue and decided to try out buefy for some useful components.
To try and keep my code organized I'm trying to make a custom component using the b-taginput.
I have it so that the component loads with the tags in someArrayofTags, but when I'm typing into the b-taginput, it does not add new tags into someArrayofTags. Hence I lose the two-way binding / updating. I would like to know where I am going wrong and how I could adjust this.
I'm not too well versed to understand how they have implemented it, but i do see that it is composed of autocomplete and b-tags https://github.com/buefy/buefy/blob/dev/src/components/taginput/Taginput.vue
I'm trying to use the custom component as such
<mytaglist v-model="someArrayofTags"></mytaglist>
I know v-model is just v-bind on value and #input events. My code is below.
<template>
<b-field label="tag inputs">
<b-taginput
:value="value"
#input=someMethod($event.target.value)
size="is-small"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear"]
};
},
methods: {
someMethod(tags) {
alert(tags)
this.$emit("input", tags)
},
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
};
</script>
Thanks
After going through the source for buefy, I found that I could watch and update the values based on a v-model within the new component.
The code below works for me, but if anyone could provide a better solution I will leave it open.
<template>
<b-field label="tag inputs">
<b-taginput
v-model="newValue"
size="is-large"
ref="ti"
>
<template slot="selected" slot-scope="value">
<b-tag
v-for="(tag, index) in value.tags"
:key="index"
:type="getType(tag)"
rounded
:tabstop="false"
ellipsis
closable
#close="$refs.ti.removeTag(index, $event)"
>
{{ tag }}
</b-tag>
</template>
</b-taginput></b-field
>
</template>
<script>
export default {
props: ['value'],
data() {
return {
normal: ["wnl","clear","deep & quiet"],
newValue: this.value
};
},
methods: {
getType(tag) {
if (this.normal.includes(tag)) {
return "is-danger";
} else {
return "is-success";
}
},
},
watch: {
newValue(value) {
this.$emit('input', value)
},
value(value) {
this.newValue = value
}
}
};
</script>
<style>
</style>

Why are checkboxes not reset by v-model?

This is what i have:
Template
<div
v-for="(filter, index) in filtersList"
:key="index"
class="option-block"
>
<label
v-for="value in filter.values"
:key="value.id"
class="option-block__container"
>
{{ value.title }}
<input
type="checkbox"
v-model="filtersValues[filter.name]"
:value="value.value"
>
<span class="option-block__checkmark"></span>
</label>
</div>
And the part of my vue code:
data() {
return {
filtersList: {},
filtersValues: {}
}
},
beforeMount() {
this.loadInitData();
this.initFilters();
},
methods: {
loadInitData() {
const data = JSON.parse(this.$el.getAttribute('data-data'));
this.filtersList = data.filters;
},
initFilters() {
for (let i in this.filtersList) {
if (!this.filtersList.hasOwnProperty(i)) {
continue;
}
this.filtersValues[this.filtersList[i].name] = [];
}
}
}
It works, but when i call initFilters() method again (for reseting) checkboxes are still selected, and i don't know why.
The way you are assigning new, empty arrays to filterValues is not reactive.
If you change your initFilters to assign an entire new value to filterValues, you don't need to worry about using Vue.set(). For example
initFilters() {
this.filtersValues = this.filtersList.reduce((vals, { name }) => ({
...vals,
[ name ]: []
}), {})
}
Demo ~ https://jsfiddle.net/cjx09zwt/
Where did filter.values come from in line 2 of template?
Anyways vue would not be able to track the changes you are making (judging from the visible code)
There are some caveats to vue 2's reactivity. Check here for more info.
TLDR; you will need to declare anything you want to be made reactive in the component's data option upfront.
HTH

How to get the context of the template?

Consider the following Vue component
<template>
<div class="myclass">
<div class="myotherclass">I am in {{getcurrentClass()}}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass() {
// code to get the class in the context of the place
// it is called in the template
}
}
}
</script>
What I am trying to understand is how to account for the "template context" in a method. In the code above getCurrentClass() would return the class of the element (this is an example, it can be explained to "name of the element", or "id of the element", ...).
Is this at all possible? If so I would appreciate even a general pointer (and I can post the response once I have a solution) - I am not sure which direction I should be looking at to start with.
You can use basic javascript dom referencing techiniques inside the mounted lifecycle hooks.
<template>
<div class="myclass">
<div id="elem" :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
},
mounted() {
// here you can get reference to a dom element by using basic javascript dom referencing techniques
const elem = document.querySelector("#elem");
element.classList ....
}
}
</script>
You can also use the Vue $refs object. If used on a child component, it will give you the reference to that child component instance. If used on a plain DOM element, it will give a reference to that element. Read more from here
<template>
<div class="myclass">
<div ref="myElem" :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
},
mounted() {
// here you can get reference to a dom element or child component context by using $refs object
const elem = this.$refs.myElem;
myElem.classList ....
}
}
</script>
You could set the currentClass = <className> in state then render it in template:
<template>
<div class="myclass">
<div :class="getcurrentClass('myotherclass')">I am in {{ currentClass }}</div>
</div>
</template>
<script>
export default {
data() {
return {
currentClass: null
}
},
methods: {
getCurrentClass(className) {
this.currentClass = className;
return className;
}
}
}
</script>

Vuex - Computed property "name" was assigned to but it has no setter

I have a component with some form validation. It is a multi step checkout form. The code below is for the first step. I'd like to validate that the user entered some text, store their name in the global state and then send then to the next step. I am using vee-validate and vuex
<template>
<div>
<div class='field'>
<label class='label' for='name'>Name</label>
<div class="control has-icons-right">
<input name="name" v-model="name" v-validate="'required|alpha'" :class="{'input': true, 'is-danger': errors.has('name') }" type="text" placeholder="First and Last">
<span class="icon is-small is-right" v-if="errors.has('name')">
<i class="fa fa-warning"></i>
</span>
</div>
<p class="help is-danger" v-show="errors.has('name')">{{ errors.first('name') }}</p>
</div>
<div class="field pull-right">
<button class="button is-medium is-primary" type="submit" #click.prevent="nextStep">Next Step</button>
</div>
</div>
</template>
<script>
export default {
methods: {
nextStep(){
var self = this;
// from baianat/vee-validate
this.$validator.validateAll().then((result) => {
if (result) {
this.$store.dispatch('addContactInfoForOrder', self);
this.$store.dispatch('goToNextStep');
return;
}
});
}
},
computed: {
name: function(){
return this.$store.state.name;
}
}
}
</script>
I have a store for handling order state and recording the name. Ultimately I would like to send all of the info from multi step form to the server.
export default {
state: {
name: '',
},
mutations: {
UPDATE_ORDER_CONTACT(state, payload){
state.name = payload.name;
}
},
actions: {
addContactInfoForOrder({commit}, payload) {
commit('UPDATE_ORDER_CONTACT', payload);
}
}
}
When I run this code I get an error that Computed property "name" was assigned to but it has no setter.
How do I bind the value from the name field to the global state? I would like this to be persistent so that even if a user goes back a step (after clicking "Next Step") they will see the name they entered on this step
If you're going to v-model a computed, it needs a setter. Whatever you want it to do with the updated value (probably write it to the $store, considering that's what your getter pulls it from) you do in the setter.
If writing it back to the store happens via form submission, you don't want to v-model, you just want to set :value.
If you want to have an intermediate state, where it's saved somewhere but doesn't overwrite the source in the $store until form submission, you'll need to create such a data item.
It should be like this.
In your Component
computed: {
...mapGetters({
nameFromStore: 'name'
}),
name: {
get(){
return this.nameFromStore
},
set(newName){
return newName
}
}
}
In your store
export const store = new Vuex.Store({
state:{
name : "Stackoverflow"
},
getters: {
name: (state) => {
return state.name;
}
}
}
For me it was changing.
this.name = response.data;
To what computed returns so;
this.$store.state.name = response.data;
I've had such an error when getting value from the store, in computed, via ...mapState(['sampleVariable']), as you. Then I've used the this.sampleVariable in <script> and sampleVariable in <template>.
What fixed the issue was to return this in data(), assign it to a separated variable, and reuse across the component the newly created variable, like so:
data() {
return {
newVariable: this.$store.state.sampleVariable,
}
}
Then, I've changed references in the component from sampleVariable to newVariable, and the error was gone.
I was facing exact same error
Computed property "callRingtatus" was assigned to but it has no setter
here is a sample code according to my scenario
computed: {
callRingtatus(){
return this.$store.getters['chat/callState']===2
}
}
I change the above code into the following way
computed: {
callRingtatus(){
return this.$store.state.chat.callState===2
}
}
fetch values from vuex store state instead of getters inside the computed hook