how to properly use Veulidate along with vuex in vue 2 - vue.js

In the following code, I use two methods on input to update both state and apply vuelidate validation rules, now validation rules doesn't applied properly, $v.newEmloyeeName.$dirty get's always true. How to fix this so both state gets updated and validation rules applied on input? I'm using vue 2
<input
type="text"
placeholder="Employee Name *"
#input="setNewEmployeeName; setName()"
:value="newEmployeeName">
<div v-if="$v.newEmloyeeName.$dirty">
<div class="text-danger" role="alert" v-if="!$v.newEmloyeeName.required">
<small>Employee Name is required</small>
</div>
</div>
...
<script>
import { required} from 'vuelidate/lib/validators'
import {mapMutations, mapState} from vuex;
export default{
validations: { newEmloyeeName: {required}},
computed: { ...mapState('addemployee',['newEmployeeName'])},
methods:{
...mapMutations('addemployee',['setNewEmployeeName']),
setName(){this.$v.newEmployeeName.$touch()}
}
</script>

v-on directive accepts expression or callback function as event handler. Considring that click is a method, #click="click" and #click="click($event)" behave the same way, with the latter being explicit.
setNewEmployeeName; setName() is JS expression and it behaves like it would be evaluated outside the template; setNewEmployeeName is not called and is a no-op, while it should be provided with field value as an argument.
It's a good practice to not distribute JS code between template and script parts of a component. Instead, it can be:
#input="setName"
and
setName(event){
this.setNewEmployeeName(event.target.value);
this.$v.newEmployeeName.$touch()
}

Related

Is is possible to call watch() on an input ref in Vue 3 to watch the input's value attribute?

I know that we can use the v-model directive on an <input> element and use watch() to trigger a function when the state of the <input>'s value attribute changes.
I'm trying, however, to watch an <input> ref. When I do, the watcher's function is executed when the element is mounted to the DOM, but does not trigger when the <input>'s value attribute changes.
Am I doing something wrong?
<script setup>
import { ref, watch, watchEffect } from 'vue'
const refA = ref(null)
watch(refA, () => {
console.log('refA changed')
console.log(refA)
console.log(refA.value)
console.log(refA.value.value)
}, { deep: true })
</script>
<template>
<input ref="refA" type="text" value="test" /> <br />
</template>
From what I know, template refs can be watched, but they only fire when the .value changes - that's the value of the ref, i.e. the HTML element, not the value of a HTML input element. That fits with what you describe, the watcher firing once at mount and never again. Also, it is not possible to deep-watch a HTML element.
It is fascinating to me how different template refs and data felt in Vue 2, when apparently the mechanism on script-side was always the same, which is now made obvious in Vue 3 composition API. But they still do different things, where one gives access to a HTML element or a Vue component instance, and the other stores a value. I guess the big difference comes from using a ref in the ref attribute, not from the way it is declared.
Anyway, if you want to bind to the value of an input, use v-model (or v-bind:modelValue and #update:modelValue), but if you need access to the node for whatever reason, use ref=. If you want to do both, you need to use both, it is not possible to use just the template ref.
I am not sure what you are trying to achieve. if you don't need to use v-model then you should use v-bind and emit the input event, like the below.
<template>
<input :value="refA" type="text" #input="refA = $event.target.value" /> <br />
</template>
This will trigger watch on every update to your input.
A template ref which you have used as part of your example cannot be used to watch the internal value of the component.See below comments thread regarding the same.

Get access to the v-slot value inside of the script tag

I am trying to show a loading indicator which is located inside of a component that contains a slot element (lets call this the wrapper component). To do this, I have a function inside the wrapper that sets the state of the indicator based on an input boolean (setSpinnerVisible()). Now, I would like to execute this function from the component that uses this wrapper. To do this, in the parent component I use the v-slot property to get a reference to the function. I would like to be able to call this function inside the mounted() function, or from a function within methods.
However, I am not able to figure out how to do this. The only way I can think of is by passing this v-slot value into a function that is executed on an event like a button press, which works, but I also want to be able to call this method from a function that is not executed by an action in the layout (e.g. in the mounted() function).
This is (a part of) my wrapper component (the function that toggles the spinner is left out for brevity):
<template>
<slot v-bind:setSpinnerVisible="setSpinnerVisible"></slot>
...
<div class="spinner" v-show="spinnerVisible"></div>
</template>
This is (a part of) the component that uses the wrapper:
<Wrapper v-slot="{ setSpinnerVisible }">
...
</Wrapper>
I would like to be able to use the value of setSpinnerVisible inside the mounted function in one way or another, something like this fictional piece of code:
<script>
export default {
mounted() {
this.setSpinnerVisible(true)
}
}
</script>
I am using Vue 2.6.11
There are several approaches you could take.
For example, you could access the parent instance and call the method you need:
this.$parent.setSpinnerVisible()
Alternatively, you could create a gateway component that uses the Wrapper, gets setSpinnerVisible and passes it as a prop to the component that needs it.
You can use dependency injection. Described here: https://v2.vuejs.org/v2/guide/components-edge-cases.html#Dependency-Injection
So, in Wrapper.vue
<template>
...
</template>
<script>
export default {
provide () {
return {
setSpinnerVisible: this.setSpinnerVisible
}
}
}
</script>
And in your child component:
<Wrapper>
...
</Wrapper>
<script>
export default {
inject: ['setSpinnerVisible'],
mounted() {
this.setSpinnerVisible(true)
}
}
</script>
The last one would be my recommended approach because it's much neater and is not anti-pattern.

Combining v-for with v-show on same element in template

I want to display a list of entries, and I have it working up through retrieving JSON from a server, parsing it, storing it in a Vuex.Store and iterating through it with v-for-"entry in this.$store.state.entries".
When a user first visits the page all entries will be visible. The next step is to filter the entries so that only matching entries remain visible. Since this filtering will be changing a lot, I want to use v-show. I have a separate component that lets users enter search terms, the server is queried, and an array of numbers—matching IDs—is returned. I want to only show entries with IDs that match the numbers in the array, queriedEntries. My template is below:
<template>
<div id="entries">
<div v-for="entry in this.$store.state.entries"
v-html="entry.content"
v-show="this.$store.state.queriedEntries.includes(entry.id)">
</div>
</div>
</template>
I get an error that I don't understand, and searching for answers hasn't yielded anything because it doesn't match the problem others have had.
[Vue warn]: Error in render: "TypeError: this is undefined"
It's the this in the v-show, but every other this works. What's up?
Your problem is occurring because you are referencing this inside your template. This is not necessary.
The first thing I recommend you do is have a read into Vuex' Getters. Further down on the same page, you'll find information about mapGetters. This will help to prevent you from directly targeting/modifying data within your state. Modification of data should be left only to Mutations and Actions.
For example, your code may look like the below:
// in your component script
...
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
allEntries: 'entries', // map state.entries to the name 'allEntries'
queriedEntries, // your other state value. You may want to convert this to a getter
// other state values if necessary
})
}
}
...
// in your component template
<template>
<div id="entries">
<div v-for="entry in allEntries"
v-html="entry.content"
v-show="queriedEntries.includes(entry.id)">
</div>
</div>
</template>
...
Here you can see that we have used mapState which helpfully generates computed getter functions from our data in the store. We can then use the property name we have assigned it to within our template.
I ended up removing this from everything but the v-for, as suggested, and the code worked. Why this causes an error in v-show and v-html is still a mystery.
Final, working code:
<div v-for="(entry, entryindex) in this.$store.state.entries"
v-bind="{id:entryindex}"
v-bind:key="entryindex"
v-show="$store.state.queryMatchedEntries[0] == -1 || $store.state.queryMatchedEntries.indexOf(parseInt(entryindex)) != -1">

Processing local components with vue

I have the following code:
<my-messages>
<message>Hello</message>
<message>World</message>
</my-messages>
For now, I did my <my-messages> component be renderized as:
<div class="Messages">
<!-- slot here -->
</div>
And I like to do the same for <message>, but the problem is that I receiving the error Unknown custom element: <message>. Except if I change my code to:
<my-messages inline-template>
<message>Hello</message>
</my-messages>
It seems a bit hack, once that I should declare the [inline-template] to all <my-messages> components, instead of it be treated directly from this component as a default rule (eg. an option as inlineTemplate: true should do the work, if it exists).
The expected render should be like:
<div class="Messages">
<div class="message">Hello</div>
<div class="message">World</div>
</div>
My component currently:
export default {
components: {
// Should process <message> sub-component.
message: require('./Messages.Message.vue'),
}
}
Edit: on reality, the inline-template seems mixing both <div>s from template, and not nesting it.
inline-template is not a hack. I think The problem is you're not registering message components at the same place where you're using my-messages component.
So the parent component that has my-messages as a child can't understand message, you need to register it in the parent too, when you use inline-template the scope changes and and whatever is inside will be treated as inner content. You can find it in the docs
EDIT
There isn't a way to have <message> to be only usable as a child of <my-messages>, you could however throw an exception if it's misused
mounted() {
if (!this.$parent.$el.classList.contains('my-message')) {
this.$destroy();
throw new Error('You must wrap the message in a my-message');
}
}
Note that this supposes that the class my-message is available in the root element, this way you can use any wrapper element.

closure within v-for, attribute interpolation

I have this basic setup
<div v-for="n in 4">
<some-component #on-some-event="onSomeEvent(n)"></some-component>
</div>
the on-some-event is dispatched within some-component. but I need to know which of these components sent the message. with the setup above, only n is passed into the method. and the data that the event sends is nowhere.
I'd like to interpolate the function so that the method looks like this
onSomeEvent(n){
return (obj)=>{
console.log(`component ${n} sent ${obj}`);
};
}
but wrapping onSomeEvent with {{}} throws a warning: attribute interpolation is not allowed in Vue.js directives and special attributes.
I could just pass the n index into the component but that seems less elegant because I may not have the ability to modify some-component
I am somewhat new to Vue, so perhaps I am missing some core functionality for this type of thing?
<div v-for="n in 4">
<some-component #on-some-event="onSomeEvent | pass n"></some-component>
</div>
....
filters: {
pass(handler, n) {
return function() {
handler()(n, ...arguments)
}
}
},
methods: {
onSomeEvent() {
console.log(...arguments)
}
}
https://jsfiddle.net/2s6hqcy5/2/
You didn't miss anything, the message is correct, in Vue, you won't be able to use interpolation like that.
http://vuejs.org/guide/syntax.html#Interpolations
However, you may want to change how you manage events and pass data between components. In your example, you can just bind the data like this:
<div v-for="n in 4">
<some-component :n="n"></some-component>
</div>
In your component, declare the prop:
Vue.component('some-component', {
props: ['n'],
...
Now, inside each component, you have the n available like any other property (http://vuejs.org/guide/components.html#Props).
Then when dispatching your event, you can call it like this, with no need for a param:
onSomeEvent()
On the event itself, you can access the n:
console.log('Component' + this.n + ...)
https://jsfiddle.net/crabbly/mjnjy1jt/