Vue + Vuetify Validation issue when clearing form - javascript

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

Related

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;
},
},
},

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

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

Vuetify password confirmation not working

I need to create a registration mask and I want to make sure that the two passwords the user types in are identical before continuing but I cannot figure out how to do it in Vuetify.
I have tried creating a rule for it but it doesn't seem to work.
This is my code:
Template:
<v-row>
<v-col>
<v-flex md5>
<v-text-field v-model="pw1"
label="Password"
type="password"
:rules="pwdRules"
></v-text-field>
</v-flex>
</v-col>
<v-col>
<v-flex md5>
<v-text-field v-model="pw2"
label="Confirm Password"
type="password"
:rules="pwdConfirm"
></v-text-field>
</v-flex>
</v-col>
</v-row>
Script:
export default {
data: () => ({
pwdRules: [ v => !!v || 'Password required' ],
pwdConfirm:[ v => !!v || 'Confirm password', v => v === this.pw1 || 'Passwords do not match'],
}),
The funny thing is if I use this code snippet v => v === this.pw1 || 'Passwords do not match' it even makes Vuetify ignore the first rule which checks if the field is empty or not. If I delete this snippet the rule works correctly and checks if the field is empty but it obviously doesn't check if the two passwords are identical.
Vue component's data must be a function, not an arrow function since an arrow function doesn’t have a this. From Vue.js docs:
Don’t use arrow functions on an options property or callback, such as
created: () => console.log(this.a) or vm.$watch('a', newValue => this.myMethod()). Since an arrow function doesn’t have a this, this
will be treated as any other variable and lexically looked up through
parent scopes until found, often resulting in errors such as Uncaught TypeError: Cannot read property of undefined or Uncaught TypeError: this.myMethod is not a function.
You are referencing pw1 and pw2 which are not defined in data.
Here is Codepen

Sending an Array of Objects to a component in vuejs

I'm a begginer in web development and i'm trying to make a front end using vuejs and vuetify.
My problem is that i can't figure out why I can't pass an array of objects to a component.
My code is the following :
In my main page i got these lines to use the component :
<template>
...
<v-content>
...
<list-2 :objectsProps="clients"/>
...
</v-content>
...
</template>
-----------------------
<script>
import List2 from "./components/List2";
export default {
name: "App",
components: {
List2
},
data() {
return {
...
clients: [],
...
};
},
...
mounted() {
this.clients = [
{
title: "Client1",
description: "Unknown"
},
{
title: "Client2",
description: "Unknown"
},
{
title: "Pradier",
description: "Unknown"
}
];
}
};
</script>
And my component is like that:
<template>
<v-card>
<v-list two-line subheader>
<v-subheader>List</v-subheader>
<v-list-tile v-for="object in objects" :key="object.title" avatar>
<v-list-tile-avatar>
<v-icon x-large>account_circle</v-icon>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>{{ object.title }}</v-list-tile-title>
<v-list-tile-sub-title>{{ object.description }}</v-list-tile-sub-title>
</v-list-tile-content>
<v-list-tile-action>
</v-list-tile-action>
</v-list-tile>
</v-list>
</v-card>
</template>
<script>
export default {
name: "List2",
props: {
objectsProps: []
},
data() {
return {
};
},
computed:{
objects: function(){
return this.objectsProps
}
}
};
</script>
At this point, i don't have enough knowledge on vue to understand what is exactly going on here, but what i'm trying to do is to give a list of objects (which can be a list of clients or a list of vehicles or anything) to my component.
The List2 component shouldn't be aware of what it is displaying as long as it gets some objects with a title and a description.
I use a computed property on the component, because i don't know if it's recommended to do a v-for on the props.
And I constantly get this error :
TypeError: Cannot read property 'filter' of undefined
at render (vuetify.js:7048)
at createFunctionalComponent (vue.runtime.esm.js:4056)
at createComponent (vue.runtime.esm.js:4246)
at _createElement (vue.runtime.esm.js:4416)
at createElement (vue.runtime.esm.js:4353)
at vm._c (vue.runtime.esm.js:4485)
at List2.vue?36c9:37
at Proxy.renderList (vue.runtime.esm.js:3701)
at Proxy.render (List2.vue?36c9:13)
at VueComponent.Vue._render (vue.runtime.esm.js:4540)
Along with these warnings :
[Vue warn]: Invalid prop: type check failed for prop "objectsProps". Expected , got Array.
found in
---> <List2> at src/components/List2.vue
<VContent>
<VApp>
<App> at src/App.vue
<Root>
vue.runtime.esm.js:587 [Vue warn]: Error in render: "TypeError: Cannot read property 'filter' of undefined"
found in
---> <List2> at src/components/List2.vue
<VContent>
<VApp>
<App> at src/App.vue
<Root>
But i have no filter property in my main page or my component, so i don't get why...
So my question are : Is my approach correct, or am i doing it the wrong way?
What should I do to in order to make it work ?
And if you have some tips/advices for a beginner, i'm definetly up to get them !
Thank you !
You probably want to define your property with a default value
props: {
objectsProps: {
type: Array,
default() {
return [];
}
}
}
BTW:
I use a computed property on the component, because i don't know if it's recommended to do a v-for on the props.
No need for a computed property
This is what's happening in your code...
Vue allows specifying a prop of a specific data type like this:
props: {
foo: String
}
or with multiple valid types like this:
props: {
foo: [String, Number] // foo can be a `String` or a `Number`
}
The type array ([String, Number] in the example above) should not be empty:
props: {
foo: [] // invalid: missing types
}
This is how to fix it...
If you were attempting to enable type checking for the prop so that Vue enforced values of type Array, the correct syntax would be:
props: {
foo: Array
}
Or if you were trying to specify an empty array as the default prop value:
props: {
foo: {
default: () => []
}
}
You could even do both:
props: {
foo: {
type: Array,
default: () => []
}
}
demo
But i have no filter property in my main page or my component, so i don't get why...
Although I wasn't able to reproduce that error with the code shown in question, I'm guessing you have a third-party library/component that perhaps uses Array#filter on your array prop.
I had this empty section in my list :
<v-list-tile-action>
</v-list-tile-action>
And that was causing the filter error, i just had to delete it, thank you for your answers !

How can I get the value of v-select field on form submit?

I am trying to get the value of a select field in my Vue project. I have tried several things but a few of the things I still have links for are:
https://codepen.io/johnjleider/pen/jwoNrG
Get selected value in dropdown list using JavaScript?
I still haven't been able to get the value select. Is there anyway I can capture the selected value in the FormData object? I would think this would make it the easiest to grab.
In template:
<v-form #submit.prevent="update" id="exapi">
<v-select
id="select"
name="select"
v-bind:items="selectOptions"
v-model="selectedOption"
label="Exchange"
autocomplete
>
</v-select>
<v-text-field
name="input-api-input"
label="Enter key"
hint="Key"
v-model="password"
:append-icon="e1 ? 'visibility' : 'visibility_off'"
:append-icon-cb="() => (e1 = !e1)"
:type="e1 ? 'password' : 'text'"
>
</v-text-field>
<v-btn
color="secondary"
:loading="loading"
#click.native="loader = 'loading'"
:disabled="loading"
type="submit"
>
Change API Keys
</v-btn>
</v-form>
In script I have tried multiple methods but still no result:
updateExchangeApi() {
const form = new FormData(document.getElementById('exapi'));
const key = form.get('input-api-input');
const selectValue0 = form.get('select')
const e = document.getElementById('exchange-select');
const selectValue1 = e.options[e.selectedIndex].text;
console.log('in updateExchangeApi()');
// console.log(exchange);
console.log(key);
}
Is there a best practice I am missing? Any help/advice would be greatly appreciated.
UPDATE:
I forgot to include that I did try setting v-model="selectedOption" with selectedOption in data but still not able to get the value:
data: () => ({
loader: null,
LineChart,
BarChart,
UserTable,
selectedOptions: [],
selectedOption: '',
})
But when I log selectedOption form my form submit function I get undefined? console.log(self.selectedOption);
Instead of using FormData you can create a form object in vue data function.
Inside form object you can declare all the properties that will have the values of your form inputs.
Then you can access the form object on your submit function using this.form
Example Below:
<template>
<v-form #submit.prevent="submit">
<v-select
v-model="form.selectedItems"
:items="items"
multiple
label="Select"
item-value="value.foo"
item-text="text"
></v-select>
<v-text-field v-model="form.text"></v-text-field>
<v-btn type="submit">submit</v-btn>
</v-form>
</template>
<script>
export default {
data: function () {
return {
items: [
{ text: "Item 1", value: { foo: "item", bar: 2 } },
{ text: "Item 2", value: { foo: "test", bar: 42 } },
{ text: "Item 3", value: { foo: "foobar", bar: 4 } },
],
form: {
selectedItems: [],
text: "",
},
};
},
methods: {
submit() {
const formData = this.form;
// log the form data
console.log(formData);
//... handle data here
}
}
}

Categories

Resources