Validate value prop before method on #input event runs? VeeValidate/Vue - javascript

Code Sandbox with exact example
https://codesandbox.io/s/veevalidate-components-vuetify-iftco
In the example above, when I enter a wrong value in the input field, the Validation state returns Valid == true, but it should return false.
I understand that this happens because the #input event method (resize) will run first and then it will assign the value to :value. In other words, vee-validate checks the existing value before the event is fired.
Not sure how to fix it so that the input value is first validated and then the method is run!
How to replicate problem:
Open the console
Change the value of the width field to 5
You will successfully get an error message under the field but the Valid flag in the console is set to true, even though the field is not valid after the method is done.
I am not sure how to fix this. I have been trying for hours..
<template>
<v-app>
<v-row>
<v-col v-for="(value, key) in fixture.dimensions" :key="key">
<ValidationProvider
:rules="`between:${fixture.limits[key].min},${fixture.limits[key].max}`"
v-slot="{ errors, valid }"
>
<v-text-field
:value="fixture.dimensions[key]"
#input="resize(key, valid)"
:label="key"
ref="key"
:min="fixture.limits[key].min"
:max="fixture.limits[key].max"
:error-messages="errors"
outlined
type="number"
></v-text-field>
</ValidationProvider>
</v-col>
</v-row>
</v-app>
</template>
<script>
import { ValidationProvider } from "vee-validate";
export default {
name: "App",
components: {
ValidationProvider
},
data: () => ({
fixture: {
dimensions: {
width: 1000,
height: 1500
},
limits: {
width: {
min: 300,
max: 1500
},
height: {
min: 300,
max: 1500
}
}
}
}),
mounted() {
console.log(this.fixture);
},
methods: {
resize(key, valid) {
console.log("valid?", valid);
this.fixture.dimensions[key] = event.target.value;
// this.fixture.iconObject.resize()
}
}
};
</script>

If you are not using v-model to manage the input, you should explicitly call validate yourself, like this:
<ValidationProvider
:rules="`between:${fixture.limits[key].min},${fixture.limits[key].max}`"
v-slot="{ errors, valid, validate }"
>
<v-text-field
:value="fixture.dimensions[key]"
#input="resize(key, $event, validate)"
...
></v-text-field>
</ValidationProvider>
resize(key, value, validate) {
validate(value).then(result => {
console.log("valid???");
console.log(result.valid);
//either way update value
this.fixture.dimensions[key] = value;
});
}
In the callback from validate, you get a result object that includes whether the result is valid, and also which rules failed (in result.failedRules) and any error messages in an array (in result.errors). See it working here:
https://codesandbox.io/s/veevalidate-components-vuetify-ynll5

Related

VUE change value only after clicking submit

I have a code like this. In this code, a user wants to change the current name. The current name is also displayed at top of the page.
// template
<div>{{ currentUser.name }}</div>
<v-text-field
required
v-model="currentUser.name"
class="mb-3"
></v-text-field>
<v-btn>submit</v-btn>
//script
data() {
currentUser: null
},
methods: {
getUser(id) {
//my GET method here
},
updateUser() {
//my PUT method here
}
The data is from an API. The current v-text-field is filled with the current name. Right now, if I change the value in the text field, the div value also changes. How to make it change only when the user has already clicked (let's say) a submit button and the process succeed?
This may work fine
<template>
<div>
<div>{{ currentUser.name }}</div>
<v-text-field required class="mb-3" ref="textField"></v-text-field>
<button #click="updateUsername">update user's name</button>
</div>
</template>
<script>
export default {
data() {
return {
currentUser: {
name: '',
},
}
},
methods: {
updateUsername() {
this.currentUser.name = this.$refs.textField.internalValue
},
},
}
</script>
You could also use a debounce, store it in another state but having to use a $refs is okay here.
Also, I'm not a Vuetify user, hence I'm not sure what all of those value are about but you have some nice choice overall.
Adding on to Kissu's answer, if you wish to change the value on blur (when you click away), you have to do the following.
Since Vuetify does not provide a lazy prop to only allow value update on change event, you have to do it yourself. Use the :value prop and bind it to a computed property and provide a getter setter to the computed property.
This will only trigger the change on blur, when you click away from the input, or press enter or press tab.
<template>
<div>{{ currentUserName }}</div>
<v-text-field
required
:value="currentUserName"
#change="onNameChange"
class="mb-3"
></v-text-field>
</template>
<script>
...
methods: {
onNameChange(event) {
this.currentUserName = event.target.value;
}
}
computed: {
currentUserName: {
get() {
return this.currentUser.name
},
set(newName) {
this.currentUser.name = newName;
}
}
}
...
</script>
<div>{{ currentUser.name }}</div>
<v-text-field
required
:value="currentUser.name"
#input="nameChanged" //here maybe #change="xxx"
class="mb-3"
></v-text-field>
<v-btn #click="updateUser">submit</v-btn>
//script
data() {
currentUser: null
},
methods: {
nameChanged(e) {
console.log(`e`,e) //The value is based on the printed value
this.tempName = e
},
updateUser() {
this.currentUser.name = this.tempName
}
}

How to pass old value to setter if nothing is changed in computed property vuejs?

I'm learning to use Vuejs so don't mind me!
I have set getter and setter inside computed property and use it inside form with v-model. My problem is, if I change something inside the v-text-field then I can make patch request but if I don't change anything and leave it the value I got from the state then I can't make the patch request as it said the field may not be null.
How can I leave default value (value I get from state) to the v-text-field and be able to make patch request if i don't want to change anything inside the field.
my vue component.vue
<template>
<div id="update-post">
<v-dialog v-model="updatePostModal" max-width="600">
<v-card class="px-5 py-5">
<v-form ref="form" v-on:submit.prevent="update">
<v-text-field
type="text"
v-model="title" <---
label="Title"
required
></v-text-field>
</v-form>
<v-card-actions>
<v-btn color="green darken-1 white--text" #click="update">
Update
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import { getAPI } from "../axios-base";
export default {
name: "UpdatePostModal",
data() {
return {
updateTitle: null,
};
},
computed: {
title: {
get() {
var title = this.$store.state.APIData.postTitle;
return title;
},
set(value) {
this.updateTitle = value;
},
},
},
methods: {
update: function() {
var id = this.$store.state.APIData.postID;
getAPI
.patch(
`/api/posts/${id}/`,
{
title: this.updateTitle,
},
{
headers: {
Authorization: `Bearer ${this.$store.state.accessToken}`,
},
}
)
},
};
</script>
It seems like you have a few mistakes. First one, in here:
.patch(
`/api/posts/${id}/`,
{
title: this.updateTitle,
},
You are using the variable defined in data in your request. You should instead use the computed property so, it should be:
.patch(
`/api/posts/${id}/`,
{
title: this.title,
},
Next, the way you are using the state is also not right. If you are reading the computed property from the state you should always set it via the state as well. Otherwise, you'd end up with some unexpected behavior as your app grows. In order to do that you can do something like this:
get() {
// read from the state
},
set(value) {
// create an action to update the post title in the state
store.dispatch(
'updatePostTitle',
value
);
}
If you still don't want to do this, as a workaround you can address your problem like this (keeping in mind to fix the first issue mentioned above):
computed: {
title: {
get() {
return this.updateTitle || this.$store.state.APIData.postTitle;
},
set(value) {
this.updateTitle = value;
},
},
},

Dynamic binding of v-model to computed property

I have a component where i'd like to iterate over elements with a computed property.
Under normal circumstances you'd do something like this:
// Computed property
acquiredPrice: {
get () {
return value
},
set (value) {
// set the value with some vuex magic
},
},
And then reference it in the template like this:
<v-text-field
v-model="acquiredPrice"
>
</v-text-field>
And this works just as expected. However i would like to do the following
// computed property
steps () {
return [
{
allowed: true,
text: 'Some question?',
type: 'integer',
model: this.acquiredPrice, // reference to the computed property
},
]
},
<!-- inside template -->
<template v-for="step in steps">
<v-row
:key="step.text"
>
<v-col>
<h4>{{step.text}}</h4>
<!-- This does not work. Only in retrieving the value -->
<v-text-field
v-model="step.model"
>
</v-text-field>
</v-col>
</v-row>
</template>
So the core issue is that when i iterate over the steps and use the step.model to reference the computed property, i loose the setter. I.e when typing into the field the setter method is never hit.
Maybe there is some way to access computed properties by string names, so i can avoid the actual value in the dict?
I.e something like (this is just pseudo code for what i want) v-model=$computed['acquiredPrice']
A full PoC to illustrate the issue can be seen here:
<template>
<div class="">
<template v-for="step in steps">
<v-row
:key="step.text"
>
<v-col>
<h4>{{step.text}}</h4>
<!-- This does not work. Only in retrieving the value -->
<v-text-field
v-model="step.model"
>
</v-text-field>
</v-col>
</v-row>
</template>
<!-- This works just as expected -->
<v-text-field
v-model="acquiredPrice"
>
</v-text-field>
</div>
</template>
<script>
export default {
name: 'redacted',
props: {
},
data: () => ({
}),
computed: {
acquiredPrice: {
get () {
return value
},
set (value) {
// set the value with some vuex magic
// THIS IS NEVER HIT WHEN IT IS REFERENCED FROM step.model ON LINE 13
},
},
steps () {
return [
{
allowed: true,
text: 'Some question?',
type: 'integer',
model: this.acquiredPrice,
},
]
},
},
components: {
},
methods: {
},
mounted () {
},
}
</script>
Root issue is in this line:
model: this.acquiredPrice, // reference to the computed property
Because you are not assigning reference to acquiredPrice computed property into model, you are calling its getter and assigning value it returns...
There is nothing like $computed because its not needed - computed prop is just another member of your component so you can access it like this["acquiredPrice"] and since this is not available in templates, you can use a little trick using the method and returning this from it....
Whole solution:
new Vue({
data() {
return {
counter: 10
};
},
computed: {
acquiredPrice: {
get() {
return this.counter;
},
set(value) {
// just placeholder as we dont have Vuex here
this.counter = value;
console.log("Updating counter", value)
}
},
steps() {
return [{
allowed: true,
text: 'Some question?',
type: 'integer',
model: 'acquiredPrice'
}, ]
},
},
methods: {
self() {
return this
}
}
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{ counter }}
<div v-for="step in steps" :key="step.text">
<h4>{{step.text}}</h4>
<input v-model="self()[step.model]" />
</div>
</div>

VueJS - use v-model with computed property?

I try to use a v-select input that uses the data I receive from my Vuex store. I have to use a computed property, since my data gets passed from an API.
My template looks like this.
<template>
<v-card v-if="isMounted">
<v-select
v-model="chartData.selected"
:items="chartData.items"
label="Select Item"
multiple
>
</v-select>
{{chartData.selected}}
</v-card>
</template>
<script>
import {mapState} from "vuex"
export default {
data: function () {
return {
isMounted: false,
value: []
}
},
computed: {
...mapState(["clusterValues"]),
chartData() {
return {
items: this.clusterValues.data,
selected: this.clusterValues.data,
}
}
}
}
</script>
I binded this computed property to v-model. However it does not update accordingly. I guess it does not work out with a computed property. (Vue.js - Input, v-model and computed property)
v-model="value". This works, but I it does not allow me to start with every item selected.
Starting like this does not work out: value: this.$store.state.clusterValues
How can I solve this problem?

Vue + Vuetify Validation issue when clearing form

I am trying to build some simple CRUD functionality for my app and I want to re-use the same form for both create and update.
my model im updating is Menu.
The way I am doing this (please let me know if there is a better way) is by having a method openForm(menu = null) on the new button I simply dont pass a menu and on the edit button I do.
openForm then checks if menu is null and if it is creates an empty menu object.
This is then stored in data() as menuForForm.
My form receives this as a prop and that is used for rendering my form.
My problem is that when I use the Vuetify $refs.form.reset() method to clear the form I get a whole load of errors relating to null values. It seems this is due to the validation rules as if I remove them its ok.
I can't understnad why a field value being null causes these problems, as if I bind a form to normal data() fields it works fine.
Here is my code:
Parent component
<template>
<v-flex xs12 sm6 lg4>
<v_form
v-if="menuForForm"
:menu="menuForForm"
>
</v_form>
</v-flex>
</template>
<script>
data() {
return {
menuForForm: null,
newMenu: {
id: '',
label: '',
url: '',
},
}
},
methods: {
openMenuForm(menu = null) {
menu ? this.menuForForm = JSON.parse(JSON.stringify(menu)) :
this.menuForForm = this.newMenu;
this.$store.dispatch('setShowForm', true);
},
}
</script>
Form component
<template>
<v-form ref="form" v-model="valid">
<v-text-field
v-model="menu.label"
:rules="labelRules"
label="Label"
required
>
</v-text-field>
<v-btn
color="primary"
:disabled="!valid"
#click="submit"
>
submit
</v-btn>
<v-btn
#click="clear"
>
clear
</v-btn>
</v-form>
</template>
<script>
data(){
return{
valid: true,
labelRules: [
v => !!v || 'Name is required',
v => v.length >= 3 || 'Label must be atleast than 3 characters'
],
}
},
methods:{
clear() {
this.$refs.form.reset();
}
}
</script>
Here is the error I get one I click clear:
[Vue warn]: Error in callback for watcher "value": "TypeError: Cannot read property 'length' of null"
found in
---> <VTextField>
[Vue warn]: Error in nextTick: "TypeError: Cannot read property 'length' of null"
found in
---> <VTextField>
TypeError: Cannot read property 'length' of null
at labelRules (Form.vue?c13f:61)
does anyone have any idea why this is happening, I have been on this for hours and its driving me mad.
Your rules should be
data(){
return{
valid: true,
labelRules: [
v => !!v || 'Name is required',
v => (v && v.length >= 3) || 'Label must be atleast than 3 characters'
],
}
}
Because on reset, form will set value to null
Demo: https://codepen.io/ittus/pen/KRGKdK

Categories

Resources