Get data from parrent component - vue.js

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>

Related

<b-form-input> bind value does not update on the second time

I am currently implementing a component that update parent's year[] array when year to / year[1] value is lower than year from / year[0] with <b-input> (Bootstrap Vue Library).
The year to stop updating after the second time.
Code example are as below.
Code equivalent in jsfiddle can be found here.
Parent.vue
<template>
<child :year="year" #update="update">
</template>
<script>
// Import child component here
export default {
name: 'Parent',
components: {
Child,
},
data: () => ({
year: [100, null],
}),
methods: {
update(newYear) {
this.year = newYear;
},
},
</script>
Child.vue
<template>
<div>
From <b-input :value="year[0]" />
To <b-input :value="year[1]" #change="update" />
</div>
</template>
<script>
export default {
name: 'Child',
props: {
year: {
type: Array,
required: true,
}
},
methods: {
update(yearToVal) {
const [yearFrom] = this.year;
let newYear = [...this.year];
if (yearToVal < yearFrom) {
/* Both of this update end up the same */
// newYear[1] = yearFrom;
this.$set(newYear, 1 , yearFrom);
}
this.$emit('update', newYear);
},
},
};
</script>
I had used Vue Dev Tools to check and the child is emitting data correctly to the parent.
The issue happen on the vModalValue and localValue of the <b-input> are not updating on the second time.
What am I doing wrongly or is it a Bootstrap Vue library problem?
Hiws's answer indicate that the this problem does not only happen on <b-form-input> but ordinary <input> with Vue as well.
This happen due to Vue not able to react to changes since the update is happening on child, hence when year to is lower than year from, parent will not detect any changes on the second time as the array pass to Parent.vue will always be [100,100].
The solution will be using watcher on Parent.vue's array to detect the changes, hence both eg: [100, 1] -> [100,100] are both reflected on Parent.vue and most importantly, force the component to re-render.
Without force re-rendering, [100,1] or [100,2]... will always be treated as [100,100], the same value, and Vue will not react or even update to them.
Jsfiddle equivalent solution can be found here
Code sample below:
Parent.vue
<template>
<child :year="year" #update="update">
</template>
<script>
// Import child component here
export default {
name: 'Parent',
components: {
Child,
},
data: () => ({
year: [100, null],
yearKey: 0,
}),
watch: {
year: {
handler(val) {
if (val[1] < val[0]) {
let newYear = [...val];
newYear[1] = val[0];
this.year = newYear;
// key update reference: https://michaelnthiessen.com/force-re-render/
this.yearKey += 1;
}
}
}
},
methods: {
update(newYear) {
this.year = newYear;
},
},
</script>
Child.vue
<template>
<div>
From <b-input :value="year[0]" />
To <b-input :value="year[1]" #change="update" />
</div>
</template>
<script>
export default {
name: 'Child',
props: {
year: {
type: Array,
required: true,
}
},
methods: {
update(yearToVal) {
const [yearFrom] = this.year;
let newYear = [...this.year];
newYear[1] = yearToVal;
this.$emit('update', newYear);
},
},
};
</script>

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

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>

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

Properly alert prop value in parent component?

I am new to Vue and have been very confused on how to approach my design. I want my component FileCreator to take optionally take the prop fileId. If it's not given a new resource will be created in the backend and the fileId will be given back. So FileCreator acts as both an editor for a new file and a creator for a new file.
App.vue
<template>
<div id="app">
<FileCreator/>
</div>
</template>
<script>
import FileCreator from './components/FileCreator.vue'
export default {
name: 'app',
components: {
FileCreator
}
}
</script>
FileCreator.vue
<template>
<div>
<FileUploader :uploadUrl="uploadUrl"/>
</div>
</template>
<script>
import FileUploader from './FileUploader.vue'
export default {
name: 'FileCreator',
components: {
FileUploader
},
props: {
fileId: Number,
},
data() {
return {
uploadUrl: null
}
},
created(){
if (!this.fileId) {
this.fileId = 5 // GETTING WARNING HERE
}
this.uploadUrl = 'http://localhost:8080/files/' + this.fileId
}
}
</script>
FileUploader.vue
<template>
<div>
<p>URL: {{ uploadUrl }}</p>
</div>
</template>
<script>
export default {
name: 'FileUploader',
props: {
uploadUrl: {type: String, required: true}
},
mounted(){
alert('Upload URL: ' + this.uploadUrl)
}
}
</script>
All this works fine but I get the warning below
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:
"fileId"
What is the proper way to do this? I guess in my situation I want the prop to be given at initialization but later be changed if needed.
OK, so short answer is that the easiest is to have the prop and data name different and pass the prop to the data like below.
export default {
name: 'FileCreator',
components: {
FileUploader
},
props: {
fileId: Number,
},
data() {
return {
fileId_: this.fileId, // HERE WE COPY prop -> data
uploadUrl: null,
}
},
created(){
if (!this.fileId_){
this.fileId_ = 45
}
this.uploadUrl = 'http://localhost:8080/files/' + this.fileId_
}
}
Unfortunately we can't use underscore as prefix for a variable name so we use it as suffix.

vue.js wrapping components which have v-models

I have a 3rd party input component (a vuetify v-text-field).
For reasons of validation i prefer to wrap this component in my own.
my TextField.vue
<template>
<v-text-field
:label="label"
v-model="text"
#input="onInput"
#blur="onBlur"
:error-messages="this.getErrors(this.validation, this.errors)"
></v-text-field>
</template>
<script>
import VTextField from "vuetify/es5/components/VTextField";
import {vuelidateErrorsMixin} from '~/plugins/common.js';
export default {
name: "TextField",
props: ['label', 'value', 'validation', 'errors'],
mixins: [vuelidateErrorsMixin], //add vuelidate
data: function() {
return {
'text': this.value
}
},
components: {
VTextField
},
methods : {
onInput: function(value) {
this.$emit('input', value);
this.validation.$touch();
},
onBlur: function() {
this.validation.$touch();
}
},
watch: {
value: {
immediate: true,
handler: function (newValue) {
this.text = newValue
}
}
}
}
</script>
which is used in another component
<template>
...
<TextField v-model="personal.email" label="Email"
:validation="$v.personal.email" :errors="[]"/>
...
</template>
<script>
...imports etc.
export default { ...
data: function() {
return {
personal: {
email: '',
name: ''
}
}
},
components: [ TextField ]
}
</script>
This works fine but i wonder if there is a much more cleaner approach than to replicate the whole v-model approach again. As now my data is duplicated in 2 places + all the extra (non needed) event handling...
I just want to pass the reactive data directly through to the v-text-field from the original temlate. My TextField doesn't actually need access to that data at all - ONLY notified that the text has changed (done via the #input, #blur handlers). I do not wish to use VUEX as this has it's own problems dealing with input / forms...
Something more close to this...
<template>
<v-text-field
:label="label"
v-model="value" //?? SAME AS 'Mine'
#input="onNotify"
#blur="onNotify"
:error-messages="this.getErrors(this.validation, this.errors)"
></v-text-field>
</template>
<script>
import VTextField from "vuetify/es5/components/VTextField";
import {vuelidateErrorsMixin} from '~/plugins/common.js';
export default {
name: "TextField",
props: ['label', 'validation', 'errors'], //NO VALUE HERE as cannot use props...
mixins: [vuelidateErrorsMixin], //add vuelidate
components: {
VTextField
},
methods : {
onNotify: function() {
this.validation.$touch();
}
},
}
</script>
I cannot find anything that would do this.
Using props + v-model wrapping is what i do.
You need to forward the value prop down to the wrapped component, and forward the update event back up (see https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components for more details):
<template>
<wrapped-component
:value='value'
#input="update"
/>
</template>
<script>
import wrappedComponent from 'wrapped-component'
export default {
components: { 'wrapped-component': wrappedComponent },
props: ['value'],
methods: {
update(newValue) { this.$emit('input', newValue); }
}
}
</script>
Somewhere else:
<my-wrapping-component v-model='whatever'/>
I've create a mixin to simplify wrapping of a component.
You can see a sample here.
The mixin reuse the same pattern as you with "data" to pass the value and "watch" to update the value during a external change.
export default {
data: function() {
return {
dataValue: this.value
}
},
props: {
value: String
},
watch: {
value: {
immediate: true,
handler: function(newValue) {
this.dataValue = newValue
}
}
}
}
But on the wraping component, you can use "attrs" and "listeners" to passthrough all attributes and listener to your child component and override what you want.
<template>
<div>
<v-text-field
v-bind="$attrs"
solo
#blur="onBlur"
v-model="dataValue"
v-on="$listeners" />
</div>
</template>
<script>
import mixin from '../mixins/ComponentWrapper.js'
export default {
name: 'my-v-text-field',
mixins: [mixin],
methods: {
onBlur() {
console.log('onBlur')
}
}
}
</script>