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

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

Related

How to use :value and v-model together

I need to use :value and v-model together. I've got a form and on the following component I'm receiving an 'age' as a query parameter because I'm filling a previous input somewhere else. I want that age to auto-populate the input on this component. But what if the user changes that value? I'd like to re-store that new age and emit it to the parent .Vue file. How can I do this?
In other words: what if I'm getting a '3' as the age, the auto-populated input shows a '3' but then the user notices they wanted to write '30' instead of '3'? How can I save 30 instead of '3'?
Thanks in advance
<template>
<div>
<p>age</p>
<input type="text" :value="age" v-model="age_keyup" #keyup="send()">
</div>
</template>
<script>
export default {
data: function () {
return {
age_keyup: null,
}
},
methods: {
send(){
this.$emit("age_keyup", this.age);
}
},
props: ['age']
}
</script>
No need to bind the value to that prop just init age_keyup based on age :
<template>
<div>
<p>age</p>
<input type="text" v-model="age_keyup" #keyup="send()">
</div>
</template>
<script>
export default {
data: function () {
return {
age_keyup:null,
}
},
methods: {
send(){
this.$emit("age_keyup", this.age_keyup);
}
},
props: ['age'],
mounted(){
this.age_keyup=this.age
}
}
</script>

Vue JS Using local variable without defining in data() function

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.

VueJS: Use v-model and :value in the same time

I'am searching a way to use v-model and :value in same time on the same object.
I got this error:
:value="user.firstName" conflicts with v-model on the same element
because the latter already expands to a value binding internally.
The purpose is to set as default value the value get from the mapGetters (coming from one store) and to set the right value when the user will submit the modification. (in onSubmit)
<div class="form-group m-form__group row">
<label for="example-text-input" class="col-2 col-form-label">
{{ $t("firstname") }}
</label>
<div class="col-7">
<input class="form-control m-input" type="text" v-model="firstname" :value="user.firstName">
</div>
</div>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
data () {
return {
lang: "",
firstname: ""
}
},
computed: mapGetters([
'user'
]),
methods: {
...mapActions([
'updateUserProfile'
]),
onChangeLanguage () {
this.$i18n.locale = lang;
},
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname
}
console.log('Component(Profile)::onSaveChanges() - called', userData);
//this.updateUserProfile(userData);
},
// Function called when user click on the "Cancel" btn
onCancel () {
console.log('Component(Profile)::onCancel() - called');
this.$router.go(-1);
}
}
}
</script>
Typically you want to set the "initial" value of the v-model on the object itself, like:
data() {
return {
firstname: 'someName'
}
}
But since you're getting it from the store, you could access the specific getter object with this.$store.getters[your_object], so I would remove the :value binding and use v-model alone for this:
<div class="col-7">
<input class="form-control m-input" type="text" v-model="firstname">
</div>
<script>
export default {
data() {
return {
lang: "",
firstname: this.$store.getters.user.firstName
}
},
// ...
}
</script>
The Vue v-model directive is syntactic sugar over v-bind:value and v-on:input. This alligator.io article helped me a lot to understand how it works.
So basically your problem is that the v-model directive sets value to firstname, while you're also explicitly setting value to user.firstName.
There are a lot of ways to handle this issue. I think a fast and straightforward solution is to store the firstname as a data variable (as you're already doing), and then use only v-model with it, disregarding v-bind:value.
Then, to set the user from the store for the default username, you could set fristname as the store user's username in the created hook:
script:
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
created() {
this.firstname = this.user.username; // is this right? no used to the map getters syntax, but this is the idea
},
data () {
return {
lang: "",
firstname: ""
}
},
computed: mapGetters([
'user'
]),
methods: {
...mapActions([
'updateUserProfile'
]),
onChangeLanguage () {
this.$i18n.locale = lang;
},
// Function called when user click on the "Save changes" btn
onSubmit () {
console.log('Component(Profile)::onSaveChanges() - called');
const userData = {
firstName: this.firstname
}
console.log('Component(Profile)::onSaveChanges() - called', userData);
//this.updateUserProfile(userData);
},
// Function called when user click on the "Cancel" btn
onCancel () {
console.log('Component(Profile)::onCancel() - called');
this.$router.go(-1);
}
}
}
</script>
You should only use v-model, it will create a 2-way binding with the value in your script: changing the variable in js will update the input element, interacting with the input element will update the variable.
If you want to use a default value, just set the variable to that value (wherever it may come from).

Reusable component/mixin for a form that is disabled during submit in Vue

I have many components that are basically a form that, on submit, makes a request to the server and disables its input elements until a response is received. I'd like to not have to care about this disabling every time and factor it out into something reusable. Is there a good way to do that?
For concreteness, here is a minimal example:
<form v-on:submit.prevent="send">
<fieldset :disabled="isDisabled">
<div>
<label>Name</label>
<input v-model="u.name">
</div>
<div>
<label>Email</label>
<input type="email" v-model="u.email">
</div>
</fieldset>
</form>
As you can see, handling this isDisabled state clutters up the component:
data () {
return {
u: {
name: '',
email: '',
},
isDisabled: false
}
},
methods: {
send: function () {
this.isDisabled = true
api.post('/users/create', {
name: this.u.name,
email: this.u.email
}).then(response => {
this.isDisabled = false
<do something>
}).catch(error => {
alert(error)
this.isDisabled = false
})
}
}
One idea was to make a generic Form component parametrized by the required fields and REST endpoint, passed in by props. However, the forms and their send functions vary considerably, and might also include conditional inputs so this seems difficult to me.
It sounds like you want a mixin, but all it would do is declare the isDisabled data item (which I would recommend you call saving so that it better indicates program state).
Since you set it to false in both the resolve and reject phases of the Promise, you can move it to the finally phase, which would help the perceived clutter a bit.
You could possibly have a directive in your mixin that would find all the form elements in the form and disable them when saving, and re-enable them afterward, so the markup in the template would just be
<form v-disable-elements="saving">
I'm quite happy with the way I ended up doing it. My form component is basically just
<template>
<form v-on:submit.prevent="send">
<fieldset :disabled="submitting">
<slot></slot>
<div class="submit">
<input type="submit" :value="submitText">
</div>
</fieldset>
</form>
</template>
plus some code for displaying error messages. The component is parametrized by props, notably the endpoint to send data to, the payload, and the text in the button.
With slots, the actual form lives in the parent component and the computation of the payload is also done there, it is easy to have forms that contain very different inputs, unlike my first idea of passing the fields, their types and their name in the payload themselves as props. For concreteness, I usually make a computed property like this:
formdata: function () {
return {
endpoint: '/events/create',
submitText: 'Create event',
payload: {
description: this.ev.description,
date: this.date
}
}
}
and pass it with
<sync-form v-bind="formdata">
The send method in the form component takes care of disabling/un-disabling, and emits an ok or err event depending on the response. Optionally, the parent component can pass in a prop that tells the form if the input is valid and can be submitted.
I created a FormContainer.vue which also passes the data as described here https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
<template>
<form
#submit.prevent="submit">
<slot v-bind:formInfo="formInfo"></slot>
</form>
</template>
<script>
export default {
props: {
onSubmit: Function
},
data () {
return {
formInfo: {
submitting: false
}
}
},
methods: {
async submit () {
try {
this.formInfo.submitting = true
const response = await this.onSubmit()
this.$emit('onSuccess', response)
} catch (err) {
console.log('err', err)
this.$emit('onError', err)
}
this.formInfo.submitting = false
}
}
</script>
In a child I can do
<template>
<form-container
:onSubmit="onSubmit"
#onSuccess="onSuccess"
#onError="onError"
v-slot="{formInfo}">
<input type="text" v-model="email" />
<button>{{ formInfo.submitting ? 'Submitting...' : 'Submit' }}</button>
</form-container>
</template>
<script>
import FormContainer from './FormContainer'
export default {
components: {
FormContainer
},
data () {
return {
email: ''
}
},
methods: {
onSubmit () {
// Your axios or grahql call or whatever
},
onSuccess (res) {
// Whatever you want to do after the submit succeeded
console.log('Submitted!', res)
},
onError (err) {
// Whatever you want to do after the submit failed
console.log('Submit failed!', err)
}
}
</script>

Is it possible to detect if a change event was triggered by a click on a Vue select element?

I have a <select>-element that has a data property bound to it using v-model in Vue.
Sometimes I want to change that value dynamically. I also have an event-listener attached to this element which is triggered on the change-event. See code example:
<template>
<div class="mySelector">
<select id="testSelect" v-model="mySelectModel"
#change="onChange($event)">
<template v-for="(item, index) in someList">
<option :class="['btn', 'btn-default', 'removing-button']" :value="index">{{item.name}}</option>
</template>
</select>
</div>
</template>
<script>
export default {
data() {
return {
mySelectModel: null
}
},
props: {
},
methods: {
customChange: function() {
this.mySelectModel = ... // some value we from somewhere else that is set dynamically on some condiftion
},
onChange: function (event) {
if (!event) return;
// DO SOMETHING THAT WE ONLY WANT TO DO ON A REAL CLICK
}
},
}
</script>
The problem I have is that when I change the data value mySelectModel dynamically, like in the customChange-method, the change event is also called, triggering the method onChange. I only want to do stuff in that method if it was really triggered by a real click, not when it was changed dynamically.
I can not find a way to distinguish between those cases when the change-event is triggered by a click or when it is just changed for some other reason. Any suggestions?
See vue-js-selected-doesnt-triggering-change-event-select-option, it appears that select does not trigger #change when v-model is updated by JS (only when the selected value is changed by user).
A directive can add the functionality
Vue.directive('binding-change', {
update: function (el, binding, vnode) {
const model = vnode.data.directives.find(d => d.name === 'model')
if (model) {
binding.value(model.value)
}
}
})
use like
<select id="testSelect"
v-binding-change="onChange"
v-model="mySelectModel"
#change="onChange($event)">
Not sure about the parameter to onChange - I'll give it a test.
Similar to this suggested solution, you can make a settable computed that you v-model in your widget:
The get function simply returns the data item
The set function does whatever you want a change in the widget to do, in addition to setting the data item
Other code can change the data item directly and will not execute the set code of the computed.
new Vue({
el: '#app',
data: {
values: ['one','two','three'],
selectedItem: 'two'
},
computed: {
wrappedSelectedItem: {
get() { return this.selectedItem; },
set(value) {
console.log("Changed in widget");
this.selectedItem = value;
}
}
},
methods: {
changeToThree() {
console.log("Stealth change!");
this.selectedItem = 'three';
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<select v-model="wrappedSelectedItem">
<option v-for="value in values" :value="value">{{value}}</option>
</select>
<button #click="changeToThree">Set to three</button>
</div>