Vue + Nuxt: problem passing (bind) dynamic data from parent to child component - vue.js

I'm new to Vue and I'm struggling to pass dynamic data from parent to child component through props. My parent component should pass a boolean (isModalVisible) to a children component through the visible prop.
In the parent, the isModalVisible changes (from false to true) when the button is clicked.
But the child component keeps the initial value.
As far as I have understood, the data() is executed when the component is created and when any value that has been bound (visible, in this case) is changed, but this does not occurs.
Anyone can help me to find out what I am missing?
Parent component:
<template>
<div>
{{ isModalVisible }}
<CoursesModal :visible="isModalVisible" />
<button #click="showModal" title="Courses">
Courses
</button>
</div>
</template>
<script>
import CoursesModal from "../components/modals/Courses.vue";
export default {
components: {
CoursesModal
},
data() {
return {
isModalVisible: false
};
},
methods: {
showModal() {
this.isModalVisible = true;
},
closeModal() {
this.isModalVisible = false;
}
},
};
</script>
Child (modal) component:
<template>
<div>
{{ visible }}
</div>
</template>
<script>
export default {
components: {
props: {
visible: {
type: Boolean,
default: true
}
}
},
data: function() {
return {
visible: false,
};
}
};
</script>
I have tried, too, to do
return {
visible: this.visible,
};
but this value is undefined.
My package.json:
"dependencies": {
"core-js": "^3.9.1",
"nuxt": "^2.15.3"
}

I think you make a mistake about the components property. The components property did not have props property and it is used to register the component. You can read further here and here
Here is the correct way to use props:
<template>
<div>
{{ visible }}
</div>
</template>
<script>
export default {
// removed the components and data property
props: {
visible: {
type: Boolean,
default: true
}
}
};
</script>

Related

how to validate child form from parent component in Vue

I have a child component which includes form:
<el-form :model="abc" ref="ruleForm" :rules="rules">
<el-form-item prop="files">
<abc-card :title="getTranslation('abc.files')">
<file-selector v-model="abc.files" />
</abc-card>
</el-form-item>
</el-form>
And I want to add simple validations to this form:
rules: function () {
return {
files: [
{
type: 'object',
required: true,
trigger: 'change',
message: 'Field required',
},
],
};
},
But my click button is in the parent component:
<files v-model="editableAbc" ref="editableTab" />
<el-button type="primary" #click="submitForm()">Create</el-button>
methods: {
submitForm() {
this.$refs.form.validate((isValid) => {
if (!isValid) {
return;
}
////API CALLS////
});
},
}
So I am trying to achieve that when the button is clicked the navigation should be rendered. How can I do that?
As per your requirement, My suggestion would be to use a ref on child component to access its methods and then on submit click in parent component, trigger the child component method.
In parent component template :
<parent-component>
<child-component ref="childComponentRef" />
<button #click="submitFromParent">Submit</button>
</parent-component>
In parent component script :
methods: {
submitFromParent() {
this.$refs.childComponentRef.submitForm();
}
}
In child component script :
methods: {
submitForm() {
// Perform validations and do make API calls based on validation passed.
// If you want to pass success or failure in parent then you can do that by using $emit from here.
}
}
The "files" component is the form you're talking about?
If so, then ref should be placed exactly when calling the 'files' component, and not inside it. This will allow you to access the component in your parent element.
<files v-model="editableAbc" ref="ruleForm" />
There is a method with the props, which was mentioned in the comments above. I really don't like it, but I can tell you about it.
You need to set a value in the data of the parent component. Next you have to pass it as props to the child component. When you click the button, you must change the value of this key (for example +1). In the child component, you need to monitor the change in the props value via watch and call your validation function.
// Parent
<template>
<div class="test">
<ChildComponent />
</div>
</template>
<script>
export default {
data() {
return {
updateCount: 0,
};
},
methods: {
submitForm() {
// yout submit method
this.updateCount += 1;
},
},
};
</script>
// Child
<script>
export default {
props: {
updateCount: {
type: Number,
default: 0,
},
},
watch: {
updateCount: {
handler() {
this.validate();
},
},
},
methods: {
validate() {
// yout validation method
},
},
};
</script>
And one more solution. It is suitable if you cannot place the button in the child component, but you can pass it through the slot.
You need to pass the validate function in the child component through the prop inside the slot. In this case, in the parent component, you will be able to get this function through the v-slot and bind it to your button.
// Parent
<template>
<div class="test">
<ChildComponent>
<template #button="{ validate }">
<button #click="submitForm(validate)">My button</button>
</template>
</ChildComponent>
</div>
</template>
<script>
import ChildComponent from "./ChildComponent";
export default {
components: {
ChildComponent,
},
methods: {
submitForm(cb) {
const isValid = cb();
// your submit code
},
},
};
</script>
// Child
<template>
<div class="child-component">
<!-- your form -->
<slot name="button" :validate="validate" />
</div>
</template>
<script>
export default {
methods: {
validate() {
// yout validation method
console.log("validate");
},
},
};
</script>

Vue.js how to check if inputs from child components are filled so parent component can able/disable a button

Hi I'm new to vuejs and I'm struggling figuring it out how to make this work.
I have 3 different child components in a parent component, each one of the child components have multiple text and radio inputs. What I want to acomplish is to be able to disable a button on the parent component if there are empty inputs or not selected radio buttons.
Can someone explain to me how could I approach this? Thank you!.
You are looking for how to emit data, opposite to passing data down through a prop.
Here is a small example for an input field.
Child1.vue
<template>
<p>
Write something:
<input v-model="inputText" />
</p>
<p>{{ inputText }}</p>
</template>
<script>
export default {
data() {
return {
inputText: '',
};
},
emits: ['emitInput'],
watch: {
inputText() {
this.checkContent();
},
},
methods: {
checkContent() {
this.$emit('emitInput', this.inputText === '');
},
},
};
</script>
App.vue (parent):
<template>
<div id="app">
<button :disabled="disabledButton">Parent Button</button>
<Child1 #emitInput="parentMethod" />
</div>
</template>
<script>
import Child1 from './Child1.vue';
export default {
name: 'App',
components: {
Child1,
},
data() {
return {
disabledButton: true,
};
},
methods: {
parentMethod(payload) {
//Since you had a 2nd parameter on line 24 in Child1, you can access it.
//We were checking if input is empty or not, returning a boolean.
this.disabledButton = payload;
},
},
};
</script>

Vue why props is undefined on component

I try to transfer data to my component using props
If I type the name in the parent, I see that the button is working properly, ie the problem is with the component.
Where am I wrong?
Thanks in advance
Parent:
HTML
<div><button #click="changeName" :name="name">Change my name</button></div>
Script:
import UserDetail from "./UserDetail.vue";
export default {
components: {
UserDetail,
UserEdit,
},
data() {
return {
name: "Eden",
};
},
methods: {
changeName() {
this.name = "EdenM";
},
},
};
Component
<template>
<div>
<p>{{ name }}</p>
</div>
</template>
<script>
export default {
props: ["name"],
};
</script>
Ooooooh!! I add the prop an wrong place!
Here is my ans:
<user-detail :name="name"></user-detail>

Element UI dialog component can open for the first time, but it can't open for the second time

I'm building web app with Vue, Nuxt, and Element UI.
I have a problem with the Element dialog component.
It can open for the first time, but it can't open for the second time.
This is the GIF about my problem.
https://gyazo.com/dfca3db76c75dceddccade632feb808f
This is my code.
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first :visible=visible></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
ModalFirst.vue
<template>
<el-dialog
title="Tips"
:visible.sync="visible"
width="30%"
>
<span>This is a message</span>
<span slot="footer" class="dialog-footer">
<a>Hello</a>
</span>
</el-dialog>
</template>
<script>
export default {
props: [ 'visible' ]
}
</script>
And I can see a warning message on google chrome console after closing the dialog.
The warning message is below.
webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:620 [Vue warn]: 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: "visible"
found in
---> <ModalFirst> at components/ModalFirst.vue
<Pages/index.vue> at pages/index.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
This is the screenshot of the warning message.
https://gyazo.com/83c5f7c5a8e4d6816c35b3116c80db0d
In vue , using directly to prop value is not allowed . Especially when your child component will update that prop value , in my option if prop will be use
for display only using directly is not a problem .
In your code , .sync will update syncronously update data so I recommend to create local data.
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'visible' ],
data: function () {
return {
localVisible: this.visible // create local data using prop value
}
}
}
</script>
If you need the parent visible property to be updated, you can create your component to leverage v-model:
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'value' ],
data() {
return {
localVisible: null
}
},
created() {
this.localVisible = this.value;
this.$watch('localVisible', (value, oldValue) => {
if(value !== oldValue) { // Optional
this.$emit('input', value); // Required
}
});
}
}
</script>
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first v-model="visible"></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
v-model is basically a shorthand for :value and #input
https://v2.vuejs.org/v2/guide/forms.html#Basic-Usage
Side-note:
You can also import your component like so:
components: { ModalFirst },
as ModalFirst will be interpreted as modal-first as well by Vue.js

Get data from parrent component

I have 2 components:
header.vue
nav.vue
In header i have button, when i click, in nav change class. But i cant get parent component :( How to do this?
index.pug:
.g#global
frameheader
.frame
navigation(:navhidden="hiddenNav", :mobileaside="asideMobile", :links=navJson)
.frame__content
block content
main.js:
import navigation from '../vue/components/nav.vue';
import frameheader from '../vue/components/frameheader.vue';
Vue.component("navigation", navigation);
Vue.component("frameheader", frameheader);
Vue.use(VueCookie);
var global = new Vue({
el: "#global",
data() {
return {
hiddenNav: false,
asideMobile: false
}
}
})
Header.vue:
In header i have two buttons, who need to change data hiddenNav and asideMobile in main.js
<template lang="pug">
header.header
.header__left(:class="{'hidden-aside': this.$root.$emit(hiddenNav)}")
a.header__logo(href="")
img(src="img/logo_smart.png", alt="")
button.header__nav(#click="hiddenNav = !hiddenNav")
span
button.header__nav.header__nav_mobile(#click="asideMobile = !asideMobile" :class="{'active': asideMobile}")
span
</template>
<script>
import VueSlideUpDown from 'vue-slide-up-down'
export default {
name: 'frameheader',
data() {
return {
active: null,
val: false
}
},
methods: {
changeMenuType() {
this.$root.$emit(hiddenNav, true);
}
}
}
</script>
Nav.vue:
In .frame__aside i try to read parrent data drom main.js, but its not work (
<template lang="pug">
.frame__aside( :class="{'hidden-aside': navhidden, 'active': mobileaside }")
</template>
<script>
import VueSlideUpDown from 'vue-slide-up-down'
export default {
name: 'navigation',
data() {
return {
active: null,
val: false
}
},
props: {
navhidden: {
type: Boolean,
default: false
},
mobileaside: {
type: Boolean,
default: false
}
}
}
</script>
The way to accomplish what you want is to take advantage of Vue's custom events.
In your #global template, we need to add listeners for toggle-hidden-nav and toggle-mobile-aside:
#global.g
frameheader(#toggle-hidden-nav='hiddenNav = !hiddenNav', #toggle-aside-mobile='asideMobile = !asideMobile', :hidden-nav='hiddenNav', :mobile-aside='asideMobile')
.frame
navigation(:nav-hidden='hiddenNav', :mobile-aside='asideMobile')
.frame__content
NOTE: I've also updated props and events to use kebab case per the Vue docs (see here and here).
In your header component, we need to
$emit the custom events when the buttons are clicked
Pass hiddenNav and asideMobile as props (for :class binding)
<template lang="pug">
header.header
.header__left(:class="{'hidden-aside': hiddenNav}")
a.header__logo(href='')
img(src='img/logo_smart.png', alt='')
button.header__nav(#click="$emit('toggle-hidden-nav')")
span
button.header__nav.header__nav_mobile(#click="$emit('toggle-aside-moble')", :class="{'active': mobileAside}")
span
</template>
<script>
export default {
...
props: {
hiddenNav: {
type: Boolean,
default: false
},
mobileAside: {
type: Boolean,
default: false
}
},
...
}
</script>
Finally, I'd fix your the class bindings in the nav component as well:
<template lang="pug">
.frame__aside( :class="{'hidden-aside': navHidden, 'active': mobileAside }")
</template>