How to work with validations of nested components inside a parent component with Vuelidate? I would like to change parentForm.$invalid if inputs in subcomponents are valid or not.
Parent:
<parent-component>
</child-component-1>
</child-component-2>
</parent-component>
validations: {
parent: WHAT HERE?
}
Child-1
<child-component-1>
</some-input>
</child-component-1>
data() {
return {
someInput: ""
};
},
validations: {
someInput: required
}
Child-2
<child-component-2>
</some-input>
</child-component-2>
data() {
return {
someInput: ""
};
},
validations: {
someInput: required
}
I might not be an expert in Vue.
If you have declared validations in the child component and you want to access it from the parent component you can use reference the child component from parent component in this way.
In parent component it would be like
<template>
<my-child ref="mychild"> </my-child>
</template>
You can access the validations declared in my-child component which is $v object using
this.$refs.mychild.$v
and then you can use validations of child component in parent components with such ease. Hope this will make the job much easier then using complex ways and it worked for me.
The simplest way to get started with vuelidate for sub-components/form is to use Vue.js dependency injection mechanism provided by provide/inject pair. The $v instance created in parent component can be shared with children component.
As you more fine tune it, you can use Vuelidate data-nesting and only pass a subset of $v to your subcomponents. This is a roughly similar approach to how Angular does with nested Forms. It would look something like:
export default {
data() {
return {
form1: {
nestedA: '',
nestedB: ''
} /* Remaining fields */
}
},
validations: {
form1: {
nestedA: {
required
},
nestedB: {
required
}
},
form2: {
nestedA: {
required
},
nestedB: {
required
}
}
}
}
Alternately, you can declare independent instances of $v for each component. In your case, you will have one for parent and two for children. When you hit the submit button, get the reference of child component using $refs and check if nested form within the child component is valid or not.
Related
Working with VUE.JS...
Having a 'theParent' component with two childs: 'theForm' and 'theButtons'.
(This is a simplification of more complex scenario).
In 'theButtons' exists a 'Clear' button to clean fields of 'theForm' form.
I am able to pass the event from 'theButtons' to 'theParent', but not from 'theParent' to 'theForm' ¿?
Within 'theButtons':
<b-button v-on:click="clean()">Clear</b-button>
methods: {
clean() {
this.$emit('clean');
}
},
Within 'theParent':
<theForm />
<theButtons v-on:clean="clean"/>
methods: {
clean() {
this.$emit('theForm.clean'); //Here is the point I dont know what to put
}
},
Within 'theForm':
methods: {
clean() {
alert("This is what I want to get executed!!");
}
},
Add a ref to the TheForm component then run its method from parent :
<theForm ref="form" />
<theButtons v-on:clean="clean"/>
methods: {
clean() {
this.$refs.form.clean(); //clean should be a method in theForm component.
}
},
Or you could add a prop to theForm component which could be updated in parent and watch in child component to clean the form :
<theForm :clean="clean" />
<theButtons v-on:clean="clean"/>
data(){
return {
clean:false
}
},
methods: {
clean() {
this.clean=clean;
}
},
inside theForm :
props:['clean'],
watch:{
clean(val){
if(val){
this.clean()
}
}
}
One thing to keep in mind is the flow of passing data from child to parent and vice versa.
In a nutshell:
Parent => Child: if you want to pass data from parent to child, use props.
Child => Parent: if you want to pass data from child to parent, use events.
One solution to your problem is to have <b-button> as a child to the <theForm> as follows:
<theForm v-on:clean="cleanForm">
<!-- form children -->
....
<theButton />
....
</theForm>
Another solution is to have an event bus that passes events between components which is totally unnecessary in your case.
Reference: https://v3.vuejs.org/guide/component-basics.html#passing-data-to-child-components-with-props
https://v3.vuejs.org/guide/component-basics.html#listening-to-child-components-events
So I have the following piss of code in my child component
props: {
prop_accounts: Array,
prop_pushing_destination: String,
prop_selected_account: String,
prop_selected: Boolean,
shop_settings: Object,
name_of_selected_account_field: String
},
data() {
return {
accounts: this._.cloneDeep(this.prop_accounts),
pushing_destination: this._.cloneDeep(this.prop_pushing_destination),
selected_account: this._.cloneDeep(this.prop_selected_account),
selected: this._.cloneDeep(this.prop_selected)
}
},
The parent props pass all the props and all seams to work well but the parent is constantly sampling the backend for changes and if they acquire it updates the props of child and although I can see that props are changed the data stays the same now if I throw the data and use props directly all works well but I get the following warning
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "selected_account"
There are two ways you could handle this: use the props directly and emit changes to the parent; or use computed properties to compute the value based on the data and props.
Emitting changes is probably what you want to do 99% of the time as any changes to the props either internal or external will change what your component is doing. Using computed props allows for changes to the props to be used only if the data hasn't been modified internally.
Computed props
props: {
defaultAccounts: Array,
defaultSelected: Boolean,
...
}
data: () => ({
customAccounts: null,
customSelected: null,
})
computed: {
accounts() {
return (this.customAccounts == null) ? this.defaultAccounts : this.customAccounts
},
selected() {
return (this.customSelected == null) ? this.defaultSelected : this.customSelected
}
}
You could even define setters on the computed props to set the value of the data properties.
Emit changes
Component:
props: {
accounts: Array,
selected: Boolean,
...
}
methods: {
accountsChanged(value) {
this.$emit('update:accounts', value)
},
selectedChanged(value) {
this.$emit('update:selected', value)
}
}
Where you use component:
<my-component :accounts.sync="accounts" :selected.sync="false"></my-component>
See Sync Modifier Documentation for more info.
I haven't tested this code so it may need tweaking to get it working correctly.
I have a Vue component that receives a bunch of props through v-bind and has multiple event handlers attached using v-on.
<SomeComponent
v-for="object in objects"
v-bind:prop1="prop1"
v-bind:prop2="prop2"
v-bind:prop3="prop3"
v-bind:key="object.created_at"
v-on:event-one="eventOne"
v-on:event-two="eventTwo"
v-on:event-three="eventThree"
/>
All works fine.
The problem is that this component can appear on different parts of the interface depending on some conditionals. It's the exact same component with the exact same props and event handlers.
Our current approach is a simple copy and paste of all the above lines, but it seems error prone and verbose, since if tomorrow we need to add another event handler (say v-on:event-four="eventFour"), it requires it to be added manually to every instance of SomeComponent in the template. The same goes for any prop change and so on.
In React we would probably wrap that component in a function and just invoke it like {renderSomeComponent()} as needed.
What would the approach be using Vue?
One approach would be to use a method to create JavaScript objects for props and events. (You could get away with computed properties except that one of your bindings depends on the object of the v-for loop.)
<SomeComponent
v-for="object in objects"
v-bind="getProps(object)"
v-on="getHandlers()"
/>
computed: {
getHandlers() {
return {
"event-one": this.eventOne,
"event-two": this.eventTwo,
"event-three": this.eventThree
};
}
},
methods: {
getProps(object) {
return {
"prop1": this.prop1,
"prop2": this.prop2,
"prop3": this.prop3,
"key": object.created_at
}
},
eventOne(). { /* ... */ },
eventTwo() { /* ... */ },
eventThree() { /* ... */ }
},
data() {
return {
prop1: /* ... */,
prop2: /* ... */,
prop3: /* ... */
}
}
I am having a data object that consists of properties unrelated to vue/the UI and data that describes the state. Now I only want the state to be reactive but I need the entire object initially in the component. I need that vue not modifies the other properties because it messes with another library accessing the other properties. (expects array but gets observer)
Is it possible to only make part of an object reactive?
class Game {
constructor() {
this.untrackedProperty = ...;
this.state = {
these: "",
should: "",
be: "",
reactive: ""
}
}
}
// vue component
<script>
export default {
data: function() {
return {
gameState: null
}
},
created() {
this.game = new Game();
this.gameState = this.game.state;
}
}
</script>
Something like that.
I meant it as "That's how I think it should work - it doesn't, but I think it describes pretty well my intentions"
How about passing into your component 2 values:
state this will be tracked
untrackedProperty this will not be tracked
Instead of passing Game as a whole object, separate those 2 values
How do I access $refs inside computed? It's always undefined the first time the computed property is run.
Going to answer my own question here, I couldn't find a satisfactory answer anywhere else. Sometimes you just need access to a dom element to make some calculations. Hopefully this is helpful to others.
I had to trick Vue to update the computed property once the component was mounted.
Vue.component('my-component', {
data(){
return {
isMounted: false
}
},
computed:{
property(){
if(!this.isMounted)
return;
// this.$refs is available
}
},
mounted(){
this.isMounted = true;
}
})
I think it is important to quote the Vue js guide:
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
It is therefore not something you're supposed to do, although you can always hack your way around it.
If you need the $refs after an v-if you could use the updated() hook.
<div v-if="myProp"></div>
updated() {
if (!this.myProp) return;
/// this.$refs is available
},
I just came with this same problem and realized that this is the type of situation that computed properties will not work.
According to the current documentation (https://v2.vuejs.org/v2/guide/computed.html):
"[...]Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed"
So, what (probably) happen in these situations is that finishing the mounted lifecycle of the component and setting the refs doesn't count as a reactive change on the dependencies of the computed property.
For example, in my case I have a button that need to be disabled when there is no selected row in my ref table.
So, this code will not work:
<button :disabled="!anySelected">Test</button>
computed: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
What you can do is replace the computed property to a method, and that should work properly:
<button :disabled="!anySelected()">Test</button>
methods: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
For others users like me that need just pass some data to prop, I used data instead of computed
Vue.component('my-component', {
data(){
return {
myProp: null
}
},
mounted(){
this.myProp= 'hello'
//$refs is available
// this.myProp is reactive, bind will work to property
}
})
Use property binding if you want. :disabled prop is reactive in this case
<button :disabled="$refs.email ? $refs.email.$v.$invalid : true">Login</button>
But to check two fields i found no other way as dummy method:
<button :disabled="$refs.password ? checkIsValid($refs.email.$v.$invalid, $refs.password.$v.$invalid) : true">
{{data.submitButton.value}}
</button>
methods: {
checkIsValid(email, password) {
return email || password;
}
}
I was in a similar situation and I fixed it with:
data: () => {
return {
foo: null,
}, // data
And then you watch the variable:
watch: {
foo: function() {
if(this.$refs)
this.myVideo = this.$refs.webcam.$el;
return null;
},
} // watch
Notice the if that evaluates the existence of this.$refs and when it changes you get your data.
What I did is to store the references into a data property. Then, I populate this data attribute in mounted event.
data() {
return {
childComps: [] // reference to child comps
}
},
methods: {
// method to populate the data array
getChildComponent() {
var listComps = [];
if (this.$refs && this.$refs.childComps) {
this.$refs.childComps.forEach(comp => {
listComps.push(comp);
});
}
return this.childComps = listComps;
}
},
mounted() {
// Populates only when it is mounted
this.getChildComponent();
},
computed: {
propBasedOnComps() {
var total = 0;
// reference not to $refs but to data childComps array
this.childComps.forEach(comp => {
total += comp.compPropOrMethod;
});
return total;
}
}
Another approach is to avoid $refs completely and just subscribe to events from the child component.
It requires an explicit setter in the child component, but it is reactive and not dependent on mount timing.
Parent component:
<script>
{
data() {
return {
childFoo: null,
}
}
}
</script>
<template>
<div>
<Child #foo="childFoo = $event" />
<!-- reacts to the child foo property -->
{{ childFoo }}
</div>
</template>
Child component:
{
data() {
const data = {
foo: null,
}
this.$emit('foo', data)
return data
},
emits: ['foo'],
methods: {
setFoo(foo) {
this.foo = foo
this.$emit('foo', foo)
}
}
}
<!-- template that calls setFoo e.g. on click -->