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

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).

Related

How to use v-model and computed properties on Input fields?

I have a simple component with 2 input fields:
<template>
<div>
<input type="text" v-model="name">
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data() {
return {
name: "",
alias: ""
}
}
}
</script>
I want to automatically insert the name model's value to the alias field IF the alias field is empty. If it's not empty, I want it to use its own value.
Here is my attempt:
<template>
<div>
<input type="text" v-model="name">
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data() {
return {
name: "",
alias: ""
}
},
computed: {
alias: {
get: function() {
if (this.alias.length < 1) {
return this.name
} else {
return this.alias
}
},
set: function(newValue) {
this.alias = newValue
}
}
}
}
</script>
The problem is that alias doesn't get the actual value in the alias property if I type something into the name field. I can only see it visually - but it doesn't hold the value. However, if I type into the alias field, it gets updated properly.
What am I missing here and how can I make it the way I want it?
Computed won't work because it should be treated as immutable.
Also because the model will be updated on each input, a watch won't work either, it would only pick up the first char of what you enter, unless its pre-populated.
This is how I would do it, simply add a #blur event on the name input then fire a method which populates alias if it's empty and on alias in case they empty it out.
The same method could be used in mounted, if you pre-populate the models, or you could watch it.
{
template: `
<div>
<input type="text" v-model="name" #blur="setAlias()">
<input type="text" v-model="alias" #blur="setAlias()">
</div>
`,
data() {
return {
name: '',
alias: ''
}
},
methods: {
setAlias() {
if (this.alias === '') {
this.alias = this.name
}
}
}
}
Firstly, you cannot have a computed property and a data property with the same name. Since both computed and data properties end up as properties on the same state object, one will overwrite the other.
Secondly, and I think you did this because of the first point, in your computed alias getter, your reference the alias again, which is essentially referencing itself, and looks like it could give some inconsistent return values.
I can think of two solutions to your issue:
1) Use a watcher on name:
Create a watcher function for name, and in it set this.alias to the same value as name when alias is blank, or if it's the same as the previous name value.
<script>
export default {
data: () => ({
name: "",
alias: ""
}),
watch: {
name(newVal, oldVal) {
if (!this.alias || this.alias === oldVal) {
this.alias = newVal;
}
}
}
}
</script>
2) Use explicit :value and #change/#keyup bindings on the name input:
v-model is a convenience method that sets both of these for you, but in your case you want to do some more logic in the change handler that just setting a state property value.
<template>
<div>
<input
type="text"
:value="name"
#keyup="onNameInput"
/>
<input type="text" v-model="alias">
</div>
</template>
<script>
export default {
data: () => ({
name: "",
alias: ""
}),
methods: {
// Check and set both values on name input events
onNameInput(e) {
if (!this.alias || this.alias === this.name) {
this.alias = e.target.value;
}
this.name = e.target.value;
}
}
}
</script>

VueJS: how to trigger 'change' on <input> changed programmatically

I'm going to build a customized virtual keyboard, so that's the first problem I've encountered.
I have an input element, whose value is changed from outside, in my case by pressing a button. The problem is that there seems to be no way to trigger the normal 'change' event.
Neither clicking outside the input, nor pressing Enter gives any result. What might be the correct way of solving this problem?
<template>
<div class="app-input">
<input #change="onChange" type="text" v-model="value" ref="input">
<button #click="onClick">A</button>
</div>
</template>
<script>
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
</script>
The whole pen can be found here.
v-on:change would only trigger on a direct change on the input element from a user action.
What you are looking for is a wathcer for your data property, whenever your value changes, watcher will execute your desired function or task.
watch: {
value: function() {
this.onChange();
}
}
The watch syntax is elaborated on the provided official vuejs docs link. use your data property as the key and provide a function as a value.
Check the snippet.
export default {
name: "AppInput",
data() {
return {
inputDiv: null,
value: ""
};
},
props: {
msg: String
},
methods: {
onClick() {
this.value = this.value + "A";
this.inputDiv.focus();
},
onChange() {
alert("changed");
}
},
// this one:
watch: {
value: function() {
this.onChange();
}
},
// --- rest of your code;
mounted() {
this.$nextTick(() => {
this.inputDiv = this.$refs.input;
});
}
};
When I build any new vue application, I like to use these events for a search input or for other inputs where I don't want to fire any functions on #change
<div class="class">
<input v-model="searchText" #keyup.esc="clearAll()" #keyup.enter="getData()" autofocus type="text" placeholder="Start Typing ..."/>
<button #click="getData()"><i class="fas fa-search fa-lg"></i></button>
</div>
These will provide a better user experience in my opinion.

Send value from API request from a component to another Vue.JS

I have a component which allow to retrieve the datas from a rest API...
My template allow user to enter an input (id) and to find the user associated with the user. I also have a component which is called dynamically.
<template>
<div>
<!-- form -->
<form>
<input type="text" v-model="userId" id="userId">
<button type="submit" class="btn btn-primary" #click="getUser($event); !isExistingUser">Get User</button>
</form>
<!-- result -->
<div v-if="!showComponent">
{{ user["id"] }} {{ user["username"] }} {{ user["email"] }}
<button #click="showComponent = !showComponent">Editer</button>
</div>
<!-- Edit the user -->
<div v-if="showComponent">
<edit-user :toUpdate="updateUser"></edit-user>
</div>
</div>
</template>
In the script part I have datas and methods :
The objective is to send the user that i collect and to send it to the update user. For this I created a data binding.
I also try to set the value of the object in the getUser method. And i can display the value.
<script>
import axios from "axios";
import EditUserForUpdate from "./EditUserForUpdate";
export default {
name: "FindUser",
components: {
"edit-user": EditUserForUpdate
},
data() {
return {
toUpdate: Object,
user: null,
isExistingUser: false,
userId: "",
userEmail:"",
userUsername: "",
showComponent: false
};
},
methods: {
getUser(event) {
axios
.get("http://localhost:4000/api/users/" + this.userId)
.then(response => {
console.log(response);
this.user = response.data.data;
var toUpdate = {};
toUpdate = { upUserName: this.user.username, upUserEmail: this.user.email, upId: this.user.id};
console.log(toUpdate);
});
}
}
};
</script>
Finally in the child component :
<script>
export default {
name: "EditUserForUpdate",
data: function () {
return {
updateUser: ''
}
},
props: {
updateUser: Object
},
methods: {
beforeMount () {
var updateUser = this.updateUser // save props data to itself's data and deal with it
console.log("userToUpdate : " + updateUser);
}
}
}
</script>
My issue is that I don't retrieve the data in the child module for an unknown reason.
The property is named toUpdate and not updateUser.
Update your prop accordingly in the EditUserForUpdate component:
props: {
toUpdate: Object
}
And of course, localize that object for manipulation:
beforeMount() {
this.updateUser = this.toUpdate
}

How to set initial value for input Vue component using v-model

I have the following component:
Vue.component('email-input', {
template: '#tmpl-email-input',
name: 'email-input',
delimiters: ['((', '))'],
props: ['name', 'required', 'value'],
data: () => ({
suggestedEmail: '',
email: '',
}),
methods: {
onInput() {
this.checkEmail();
this.$emit('input', this.email);
},
checkEmail() {
Mailcheck.run({
email: this.email,
suggested: suggestion => {
this.suggestedEmail = suggestion.full;
},
empty: () => {
this.suggestedEmail = '';
},
});
},
confirmSuggestion(confirm) {
if (confirm) this.email = this.suggestedEmail;
this.suggestedEmail = '';
},
},
mounted() {
this.checkEmail = _.debounce(this.checkEmail.bind(this), 1000);
},
});
using this template
<template id="tmpl-email-input">
<div>
<input
type="email"
class="form-control"
:name="name || 'email'"
:required="required"
v-on:input="onInput"
v-model="email"
/>
<small class="email-correction-suggestion" v-if="suggestedEmail">
Did you mean ((suggestedEmail))?
Yes
No
</small>
</div>
</template>
<!-- Lodash from GitHub, using rawgit.com -->
<script src="https://cdn.rawgit.com/lodash/lodash/4.17.4/dist/lodash.min.js"></script>
<!-- Mailcheck: https://github.com/mailcheck/mailcheck -->
<script src="/js/lib/mailcheck.js"></script>
<script src="/js/views/partials/email_input.js"></script>
And I'm calling it using
<email-input name="email" required></email-input>
I'd like to set an initial value for this email input, something like
<email-input name="email" required value="test#test.com"></email-input>
and have that show in the input.
I assumed I could do this by simply setting email to this.value in data but that doesn't help. How can I do this?
There's a value prop but you are not using it at all! So it doesn't really matter which value you pass down as value prop: it won't be used.
I think what you are trying to achieve is expose an API similar to the one exposed by input component. That can be done and it's detailed in the docs.
What Vue does to handle the v-model bindings is assuming the component will emit an input event passing the new value as $event. It will also pass down to the component a value to the value prop. So this 2-way binding is automatically handled by Vue as long as you define a value prop and emit an input event.
The problem is that your component acts as a middleware for the underlying input component but it is passing down a different binding instead of forwarding it.
Translating this into your component, you should not use v-model to pass down email to the input component but a combination of :value and #input bindings: you pass down the value prop of email-input component to the value prop of the input component and as handler of input event of the input component you should just emit another input event with the same $event payload.
Template:
<template id="tmpl-email-input">
<div>
<input
type="email"
class="form-control"
:name="name || 'email'"
:required="required"
:value="value"
#input="onInput($event.target.value)"
/>
<small class="email-correction-suggestion" v-if="suggestedEmail">
Did you mean ((suggestedEmail))?
Yes
No
</small>
</div>
</template>
<!-- Lodash from GitHub, using rawgit.com -->
<script src="https://cdn.rawgit.com/lodash/lodash/4.17.4/dist/lodash.min.js"></script>
<!-- Mailcheck: https://github.com/mailcheck/mailcheck -->
<script src="/js/lib/mailcheck.js"></script>
<script src="/js/views/partials/email_input.js"></script>
Note the change from #input="onInput" to #input="onInput($event.target.value)" so we have access to the new value in onInput method.
Component:
Vue.component('email-input', {
template: '#tmpl-email-input',
name: 'email-input',
delimiters: ['((', '))'],
props: ['name', 'required', 'value'],
data: () => ({
suggestedEmail: ''
}),
methods: {
onInput(newValue) {
this.$emit('input', newValue);
this.checkEmail();
},
checkEmail() {
Mailcheck.run({
email: this.value,
suggested: suggestion => {
this.suggestedEmail = suggestion.full;
},
empty: () => {
this.suggestedEmail = '';
},
});
},
confirmSuggestion(confirm) {
if (confirm) this.$emit('input', this.suggestedEmail);
this.suggestedEmail = '';
},
},
mounted() {
this.checkEmail = _.debounce(this.checkEmail.bind(this), 1000);
},
});
Note the change in onInput method: now it takes a parameter with the new value and emits an input event with that value before checking the email address. It's emitted in that order to ensure we have synced the value of the value binding before checking the address.
Also note the change in confirmSuggestion method: instead of updating email data attribute it just emits an input event.
That's the key to solve this issue: the old implementation forced us to have 2 different variables: one where parent component could pass down a value and another one email-input could modify to store the chosen suggestion.
If we just emit the chosen suggestion as a regular change then we can get rid of the email variable and work with just one binding.
Suggestion totally not related with the issue: you can use debounce directly in methods instead of replacing the method on mounted hook:
Vue.component('email-input', {
template: '#tmpl-email-input',
name: 'email-input',
delimiters: ['((', '))'],
props: ['name', 'required', 'value'],
data: () => ({
suggestedEmail: ''
}),
methods: {
onInput(newValue) {
this.$emit('input', newValue);
this.checkEmail();
},
checkEmail: _.debounce(function () {
Mailcheck.run({
email: this.value,
suggested: suggestion => {
this.suggestedEmail = suggestion.full;
},
empty: () => {
this.suggestedEmail = '';
},
});
}, 1000),
confirmSuggestion(confirm) {
if (confirm) this.$emit('input', this.suggestedEmail);
this.suggestedEmail = '';
},
}
});
Lodash will take care of binding this of the underlying function to the same this that called the debounced function.

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