Function inside watch option doesn't invoke on state change Vue - vue.js

I'm using vuex and I have divided it into different modules. In one of them I have states and a watcher. watcher doesn't seem to work. here is my code inside page-two/index.js file
import pageTwoMutations from "./mutations.js";
import pageTwoActions from "./actions.js";
export default {
namespaced: true,
data() {
return {
hadCovid: null,
hadAntibodyTest: null,
antibodiesTestDate: null,
antibodiesNumber: null,
covidSicknessDate: null,
};
},
mutations: pageTwoMutations,
actions: pageTwoActions,
watch: {
hadCovid(value) {
console.log("fire");
if (value === "no" || value === "have_right_now") {
this.hadAntibodyTest = null;
}
},
},
};
Here is my store's folder structure
I need to update my state with v-model(other options don't work in my case) inside child component. so I pass name of the state property and module from parent. child component's code:
<template>
<div>
<label :for="name" class="mb-4 font-bold">
{{ label }}
</label>
<div v-for="elem in options" :key="elem.value" class="ml-4">
<input-field
:id="elem.value"
v-model="$store.state[module][property]" // !! state changes here
:name="name"
type="radio"
:value="elem.value"
:rules="rules"
/>
<label :for="elem.value" class="ml-4">{{ elem.label }}</label>
</div>
<ErrorMessage class="text-red-500 text-center" :name="name" />
</div>
</template>
<script>
import { ErrorMessage } from "vee-validate";
export default {
components: {
ErrorMessage,
},
props: ["label", "module", "property", "name", "rules", "options"]
</script>
parent element's code:
<radio-input
label="some label question"
name="had_covid"
rules="required"
module="pageTwo"
property="hadCovid"
:options="[
{ label: 'yes', value: 'yes' },
{ label: 'non', value: 'no' },
{ label: 'have right now', value: 'have_right_now' },
]"
/>
state update is working fine, but watcher function doesn't fire anyways.

Related

Update child component value on axios response using v-model

Vue 3
I am trying to update the value of the data variable from the Axios response. If I print the value in the parent component it's getting printed and updates on the response but the variable's value is not updating in the child component.
What I am able to figure out is my child component is not receiving the updated values. But I don't know why is this happening.
input-field is a global component.
Vue 3
Parent Component
<template>
<input-field title="First Name" :validation="true" v-model="firstName.value" :validationMessage="firstName.validationMessage"></input-field>
</template>
<script>
export default {
data() {
return {
id: 0,
firstName: {
value: '',
validationMessage: '',
},
}
},
created() {
this.id = this.$route.params.id;
this.$http.get('/users/' + this.id).then(response => {
this.firstName.value = response.data.data.firstName;
}).catch(error => {
console.log(error);
});
},
}
</script>
Child Component
<template>
<div class="form-group">
<label :for="identifier">{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input :id="identifier" :type="type" class="form-control" :class="validationMessageClass" :placeholder="title" v-model="inputValue">
<div class="invalid-feedback" v-if="validationMessage">{{ validationMessage }}</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
validation: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
default: 'text',
},
validationMessage: {
type: String,
required: false,
default: '',
},
modelValue: {
required: false,
default: '',
}
},
emits: [
'update:modelValue'
],
data() {
return {
inputValue: this.modelValue,
}
},
computed: {
identifier() {
return this.title.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
},
validationMessageClass() {
if (this.validationMessage) {
return 'is-invalid';
}
return false;
}
},
watch: {
inputValue() {
this.$emit('update:modelValue', this.inputValue);
},
},
}
</script>
The reason your child will never receive an update from your parent is because even if you change the firstName.value your child-component will not re-mount and realize that change.
It's bound to a property that it internally creates (inputValue) and keeps watching that and not the modelValue that's been passed from the parent.
Here's an example using your code and it does exactly what it's supposed to and how you would expect it to work.
It receives a value once (firstName.value), creates another property (inputValue) and emits that value when there's a change.
No matter how many times the parent changes the firstName.value property, the child doesn't care, it's not the property that the input v-model of the child looks at.
You can do this instead
<template>
<div class="form-group">
<label :for="identifier"
>{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input
:id="identifier"
:type="type"
class="form-control"
:class="validationMessageClass"
:placeholder="title"
v-model="localValue" // here we bind localValue as v-model to the input
/>
<div class="invalid-feedback" v-if="validationMessage">
{{ validationMessage }}
</div>
</div>
</template>
<script>
export default {
... // your code
computed: {
localValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
};
</script>
We remove the watchers and instead utilize a computed property which will return the modelValue in it's getter (so whenever the parent passes a new value we actually use that and not the localValue) and a setter that emits the update event to the parent.
Here's another codesandbox example illustrating the above solution.

vuejs2 slots how to communicate with parent

I want to create a formRow component that have a slot for the input field and vee-validate for validation
this is my markup
//form
<vFormRow validate="required" v-slot="{ onInput, errors }">
<vTextfield #input="onInput" :errors="errors"/>
</vFormRow>
I tried to emit the value to the parent and get the value with #input="onInput" on the slot, but this doesn't work
//formrow
<template>
<div class="mb-4">
<v-validation-provider
v-slot="{ errors }"
:rules="validate"
>
<label>{{ label }}</label>
<slot
:onInput="onInput"
:errors="errors"
/>
<vFormError
:message="errors[0]"
/>
</v-validation-provider>
</div>
</template>
<script>
import { ValidationProvider as vValidationProvider } from 'vee-validate'
import vRadioButton from '#primitives/textfield'
import vFormError from '#primitives/formError'
export default {
components: {
vTextfield,
vFormError,
vValidationProvider
},
props: {
value: {
type: String,
default: '',
},
validate: {
type: String,
required: true,
}
},
methods: {
onInput(value) {
console.log(value)
}
}
}
</script>
//textfield
<template>
<div>
<input :value="value" #input="onInput"/>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
},
errors: {
type: Array,
default: () => {}
}
},
data: {
return {
// local_value: ''
}
}
methods: {
onInput(e) {
// this.local_value = e.target.value
this.$emit('input', e.target.value)
}
}
}
</script>
What I'm doing wrong?
You're not actually passing the slot props to <vTextfield>. I think you might be assuming that data bindings and event handlers on the <slot> are automatically applied to the slot's children, but that's not true.
To use the value slot prop, destructure it from v-slot, and bind it to the <vTextfield> child:
<vFormRow validate="required" v-slot="{ value }">
<vTextfield :value="value" />
</vFormRow>
You could also pass the onInput method as a slot prop:
// vFormRow.vue
<slot :onInput="onInput" />
Then bind the event handler to <vTextfield>:
<vFormRow validate="required" v-slot="{ onInput }">
<vTextfield #input="onInput" />
</vFormRow>

How to fire on change event of select while setting v-model?

I have Dropdown.vue and Sprint.vue use Dropdown.vue
Dropdown.vue code
<template>
<div>
<select
v-model="selected"
#input="$emit('input', $event.target.value)"
#change="emitChangeEvent"
>
<option
v-for="(option, idx) in options"
:key="`option-${idx}`"
:value="option.value"
>
{{ option.text }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'Dropdown',
props: {
options: {
type: Array,
default: () => [],
},
value: {
required: false,
},
},
computed: {
selected: {
get() {
return this.value;
},
set(newValue) {},
},
},
methods: {
emitChangeEvent() {
this.$emit('change', this.selected);
},
},
};
</script>
In Sprint.vue
<template>
<div>
<Dropdown
v-model="sprint"
:options="sprints"
#change="sprintChanged"
/>
{{ sprint }}
<button #click="change">
Change Sprint
</button>
</div>
</template>
<script lang="ts">
import {
Component, Prop, Vue, Emit,
} from 'vue-property-decorator';
import Dropdown from '#/components/Dropdown.vue';
#Component({
components: {
Dropdown,
},
})
export default class Sprint extends Vue {
private sprint = {};
private sprints = [
{ text: 'Sprint A', value: 'A' },
{ text: 'Sprint B', value: 'B' },
{ text: 'Sprint C', value: 'C' },
];
change() {
this.sprint = 'B';
}
sprintChanged(value: any) {
console.log(`value=${value}, sprint=${this.sprint}`);
}
}
</script>
When we select an option, an event was fired. But when I click on the button to set selected option by programming, there is no event of select is fired.
What am I missing?
As far as I can see, there are two main problems in your Dropdown.vue component.
1) You are binding the v-model of the element to the computed property selected, and emitting selected in your emitChangeEvent
2) and the selected computed properties doesn't have a setter so nothing happens when the value change.
To check if this is the only problem, a quick solution is to change your emitChangeEvent to this:
emitChangeEvent(event) {
this.$emit('change', event.target.value);
}
Emitting directly the value from event will skip any problem in the definition of the computed property.

Wrapping a ValidationObserver around a v-for loop

I have a v-for loop that allows me to dynamically add new fields to my form. This loop is within a tab which I need to validate before I go onto the next section of my form. It seems as though nothing renders when I place the v-for within my validation observer. Is there another way to accomplish this?
I'm using VeeValidate 3
<template>
<div>
<b-card class="mb-3">
<ValidationObserver :ref="'contact_obs'" v-slot="{ invalid }">
<div
v-for="(contact, index) in this.applicant.contacts"
:key="contact.id"
role="tablist"
>
<b-form-row>
<BTextInputWithValidation
rules="required"
class="col-md-4"
:label="
$t('contact_name', { name: applicant.contacts[index].title })
"
:name="$t('contact_name')"
v-model="applicant.contacts[index].name"
description
placeholder
/>
<BTextInputWithValidation
rules
class="col-md-4"
:label="$t('contact_title')"
:name="$t('contact_title')"
v-model="applicant.contacts[index].title"
description
placeholder
/>
<BTextInputWithValidation
rules
class="col-md-3"
:label="$t('contact_email_address')"
:name="$t('contact_email_address')"
v-model="applicant.contacts[index].email"
description
placeholder
/>
<b-button
variant="outline-danger"
class="float-right mt-4 mb-4 ml-3"
v-on:click="deleteContact(index)"
>
<span class="fas fa-user-minus"></span>
</b-button>
</b-form-row>
</div>
</ValidationObserver>
<b-button
variant="outline-success"
class="float-right mt-4 mb-4 ml-3"
v-on:click="addContact"
>
<span class="fas fa-user-plus"></span>
</b-button>
</b-card>
</div>
</template>
<script>
import { ValidationObserver } from 'vee-validate'
import VeeValidate from 'vee-validate'
import BTextInputWithValidation from './inputs/BTextInputWithValidation'
let id = 10
export default {
components: { ValidationObserver, BTextInputWithValidation },
mounted() {},
data: function() {
return {
applicant: {
contacts: [
{
id: '1',
name: '',
title: 'Primary Principal',
email: ''
},
{
id: '2',
name: '',
title: 'Secondary Principal',
email: ''
},
{
id: '3',
name: '',
title: 'Accounts Receivable',
email: ''
}
]
}
}
},
methods: {
addContact: function(params) {
this.applicant.contacts.push({
id: id,
name: '',
title: '',
email: ''
})
id++
},
deleteContact: function(index) {
this.$delete(this.applicant.contacts, index)
},
validate() {
const isValid = this.$refs.contact_obs.validate()
if (isValid) {
this.$emit('on-validate', this.$data, isValid)
}
return isValid
// return true
}
}
}
</script>
<style lang="scss" scoped></style>
I believe the problem is here:
v-for="(contact, index) in this.applicant.contacts"
In general you should avoid using the this. prefix to access properties in templates but usually it does no harm. This is one of those cases where it actually does break something. this does not refer to the correct object inside a scoped slot.
I'm surprised you don't see an error in your console.

Post Form empty data

for me NuxtJS application i've got multiple components thats come together in one form. Here the code:
AppButton.vue
<template>
<button
class="button"
:class="btnStyle"
:disabled="disabled"
v-bind="$attrs"
v-on="$listeners"><slot /></button>
</template>
<script>
export default {
name: 'AppButton',
props: {
btnStyle: {
type: String,
default: ''
},
disabled: {
type: String
}
}
}
</script>
AppFormInput.vue
<template>
<div class="form-input">
<input
v-bind="$attrs"
:name="name"
:value="value"
:type="type"
:placeholder="placeholder"
:max="max"
:min="min"
:pattern="pattern"
:required="required"
:disabled="disabled"
#input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
name: 'AppFormInput',
props: {
controlType: {
type: String,
default: 'input'
},
name: {
type: String
},
value: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
max: {
type: String
},
min: {
type: String
},
pattern: {
type: String
},
required: {
type: String,
},
disabled: {
type: String
}
}
}
</script>
FormGroup.vue
<template lang="html">
<div class="form-group">
<AppLabel :label="label"/>
<AppFormInput :v-bind="$attrs"/>
</div>
</template>
<script>
import AppLabel from '~/components/atoms/AppLabel'
import AppFormInput from '~/components/atoms/AppFormInput'
export default {
components: {
AppLabel,
AppFormInput
},
props: {
label: {
type: String,
default: 'Form Label'
}
}
}
</script>
Form.vue
<template lang="html">
<form #submit.prevent="onSave">
<FormGroup label="Form Input" v-model="formPosts.forminput"/>
<FormGroup label="Form Input Disabled" disabled="disabled" v-model="formPosts.forminputdisabled"/>
<FormGroup label="Form Input With Placeholder" placeholder="Set placeholder" v-model="formPosts.forminputplaceholder"/>
<FormGroup label="Form Input Required" required="required" v-model="formPosts.forminputrequired"/>
<FormGroup label="Form Email" type="email" v-model="formPosts.forminputemail"/>
<FormGroup label="Form Date" type="date" v-model="formPosts.forminputdate"/>
<FormGroup label="Form Number" type="number" value="1" min="1" max="5" v-model="formPosts.forminputnumber"/>
<FormGroup label="Form Tel" type="tel" pattern="\d{3}-\d{3}-\d{4}" placeholder="XXX-XXXX-XXXX" v-model="formPosts.forminputtel"/>
<!--Add Select normal and disabled-->
<FormGroup label="Form Radio" type="radio" value="1" v-model="formPosts.forminputradio"/>
<FormGroup label="Form Checkbox" type="checkbox" value="2" v-model="formPosts.forminputcheckbox"/>
<AppButton type="submit" btn-style="btn-brand">Save</AppButton>
</form>
</template>
<script>
import FormGroup from '~/components/molecules/FormGroup'
import AppButton from '~/components/atoms/AppButton'
export default {
components: {
FormGroup,
AppButton
},
data() {
return{
formPosts: {
forminput: '',
forminputdisabled: '',
forminputplaceholder: '',
forminputrequired: '',
forminputemail: '',
forminputdate: '',
forminputnumber: '',
forminputtel: '',
forminputradio: '',
forminputcheckbox: '',
}
}
},
methods: {
onSave() {
console.log(this.formPosts);
this.$emit('submit', this.formPosts)
},
}
}
</script>
At least, the index.vue
<template lang="html">
<div class="container">
<h1>Forms</h1>
<Form #submit="onSubmitted"/>
</div>
</template>
<script>
import Form from '~/components/organism/Form'
export default {
components: {
Form
},
methods: {
onSubmitted(data){
console.log(data);
}
}
}
</script>
when the form is submitted the fields stays empty. The required field must have a value for example but it stays empty. I think that the some components not have access through the value of the field. Does anyone have tips?
Thanx for any help
$attrs (in v-bind="$attrs") will only bind attributes, not props (bold is mine):
vm.$attrs: Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props. When a component doesn't have any declared props, this essentially contains all parent-scope bindings (except for class and style), and can be passed down to an inner component via v-bind="$attrs" - useful when creating higher-order components.
You need to set the props yourself in FormGroup.vue.
In FormGroup.vue, declare the value prop so you can use in the template:
<script>
import AppLabel from '~/components/atoms/AppLabel'
import AppFormInput from '~/components/atoms/AppFormInput'
export default {
components: {
AppLabel,
AppFormInput
},
props: {
label: {
type: String,
default: 'Form Label'
},
value: { // added this
type: String // added this
} // added this
}
}
</script>
In FormGroup.vue, Add :value="value" #input="$emit('input', $event)" to the template.
<template lang="html">
<div class="form-group">
<AppLabel :label="label"/>
<AppFormInput :v-bind="$attrs" :value="value" #input="$emit('input', $event)" />
</div>
</template>
The code above will set <AppFormInput>'s value and will propagate (up) it's input event. Not the v-models in Form.vue should work.