ok so I've learned that I'm not supposed to call a child's method but pass it props instead.
I've got (parent) :
<template>
<div id="main">
<Header :title ="title"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
name: 'app'
data: function () {
return {
title: true
}
},
methods: {
hideTitle: function () {
this.title = false
console.log(this.title)
},
showTitle: function () {
this.title = true
console.log(this.title)
}
}
}
</script>
and (child) :
<script>
export default {
name: 'Header',
props: ['title'],
created () {
console.log(this.title)
},
methods: {
}
}
</script>
the first console logs (inside the parent) print correctly on each method but the second console log within the child stays true all the time. I got this from : Pass data from parent to child component in vue.js
inside what method does the console.log need to be to be printed everytime the methods in the parent are triggered?
(this is why I wanted to go for method-calling, originally, by going with variables instead, we're potentially omitting valuable parts of the process such as optimization and a "when" for the execution(s!!) of our code. pontetally being the key word here, don't blow up on me, keep in mind that I'm learning.)
OLD:
I've browsed the web and I know there a a million different answers
and my point is with the latest version of vue none of those millions
of answers work.
either everything is deprecated or it just doesn't apply but I need a
solution.
How do you call a child method?
I have a 1 component = 1 file setup.
DOM is declared inside a <template> tag javascript is written inside
a <script> tag. I'm going off of vue-cli scaffolding.
latest method I've tried is #emit (sometimes paired with an #on
sometimes not) doesn't work :
child :
<script>
export default {
name: 'Header',
created () {
this.$on('hideTitlefinal', this.hideTitlefinal)
},
methods: {
hideTitlefinal: function () {
console.log('hideeeee')
},
showTitlefinal: function () {
console.log('shwowwww')
}
}
}
</script>
parent :
<template>
<div id="main">
<Header v-on:hideTitle="hideTitlefinal" v-on:showTitle="showTitlefinal"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
methods: {
hideTitle: function () {
this.$emit('hideTitle')
},
showTitle: function () {
this.$emit('showTitle')
}
}
}
</script>
console :
Uncaught TypeError: this.$emit is not a function
at Object.showTitle (Main.vue?1785:74)
at VueComponent.showTitle (LateralMenu.vue?c2ae:113)
at boundFn (vue.esm.js?efeb:186)
at invoker (vue.esm.js?efeb:1943)
at HTMLDivElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1778)
Please don't do this. You're thinking in terms of events. When x happens, do y. That's sooo jquery 2005 man. Vue still has all that stuff, but we're being invited to think in terms of a view model...
You want your state in a variable, in window scope, and you want reactive pipes linking your vue stuff to your state object. To toggle visibility, use a dynamic class binding, or v-if. Then think about how to represent your state. It could be as simple as having a property like store.titleVisible. But, you want to 'normalize' your store, and avoid relationships between items of state. So if title visibility really depends on something higher up, like an editMode or something, then just put the higher-up thing in the store, then create computed properties if you need them.
The goal is that you don't care when things happen. You just define the relationships between the markup and the store, then let Vue take care of it. The docs will tell you to use props for parent=>child and $emit for child=>parent communication. Truth is you don't need this until you have multiple instances of a component, or reusable components. Vue stuff talks to a store, not to other vue stuff. For single-use components, as for your root Vue, just use the data:.
Whenever you find yourself writing show/hide methods, you're doing it wrong. It's intuitive (because it's procedural), but you'll quickly appreciate how much better the MVVM approach is.
Related
I am using Vue 3 to build an app with a third party library (Azure Communication Calling). I'm using the options API. My issue is that I need event handlers that call functions initialized by the third party library. The only way I can find to do this is to assign the entire object created by the third party constructor to Vue's component data. I realize this is not best practice (although it does work). Is there a better way?
I am initializing the Constructor in the Mounted hook. My event handlers need access to methods inside the object created in the Constructor. It seems like you should be able to assign event handlers in Mounted, or assign methods in Mounted. I can't get either of these ideas to work.
My template is like this:
<template>
<div>
<child-component #someEvent="doSomething">
</child-component>
<div>
The rest of the app goes here..
</div>
</div>
</template>
<script>
import {Constructor} from 'third-party-library'
import ChildComponent from './components/ChildComponent'
export default {
data() {
return {
complexObject: null
}
},
components: {
ChildComponent
},
mounted() {
this.complexObject = new Constructor()
},
methods: {
doSomething() {
this.complexObject.thirdPartyMethod()
}
}
}
</script>
I just thought you weren't supposed to put objects with their own methods in data. I thought data is only for primitive data types.
That's not true. It's acceptable to return non-primitives from data(). Normally, data() is used to specify properties intended for reactivity (e.g., in the template). However, you could also specify non-reactive data by Object.freeze-ing the property:
export default {
data() {
return {
complexObject: Object.freeze(new Constructor()) // non-reactive
}
},
}
Alternatively, you could attach the property in created():
export default {
data() {
return {
// complexObject: null, ❌ remove this
}
},
created() {
this.complexObject = new Constructor() 👈
},
}
If using TypeScript, the downside here is there would be no type inference for the attached property.
Did you try to use mixins? https://vuejs.org/api/options-composition.html#mixins This way you can also use this mixin for other components too.
I solved this issue, in a similar way to Tony19's second suggestion. This turned out to be much simpler than I expected. Unless there is some reason why I should not create a new top-level property on a Vue component, this seems best.
export default {
complexObject: null,
mounted() {
this.complexObject = new Constructor()
},
}
The constructor does have to be in the mounted hook, BTW.
So I’m building a Nuxt app for working with docs (in a broad sense), and it will have a menu, which I will obviously make a component. The menu will be home to lots of actions on the doc itself, such as opening/saving files, editing, etc. etc.
I know the standard way to pass info from a component to its parent (the doc vm in this case) is via messages, but it feels like a bit of an overkill, what with the syntax (emit handlers just don’t feel natural to me in this case) and whatnot.
For this reason I was wondering why can’t I just pass the parent vm as a prop to the menu component? It will contain all kinds of methods, and I will be able to easily invoke them via the menu. Something like:
Parent (Document.vue):
<template>
<main-menu :document='vm'/>
</template>
<script>
import MainMenu from '~/components/MainMenu.vue'
export default {
data(): {
return {
vm: this,
//...
}
},
methods: {
save() {
//...
}
}
//...
</script>
Menu component (MainMenu.vue):
<template>
<button #click='document.save()'>Save document</button>
</template>
<script>
export default {
props = ['document']
}
</script>
The question: Is there something intrinsically bad in this approach?
(I imagine this could be problematic if the app architecture could change, but it’s hard to imagine that I would for some reason need a menu without an underlying document.)
IF your Menu is always the child of the component, then you don't have to pass your parent. It is already held in a Vue variable called this.$parent.
I made a little sandbox to give you an example.
The parent has a function, for example:
/// PARENT
export default {
name: "App",
components: {
HelloWorld,
},
methods: {
iExist(add) {
console.log("I am in parent" + add);
},
},
};
Then you can call it from child with this.$parent.iExist('something').
Since this.$parent is not defined when the template is being evaluated, we have to make a method in the child as well, to call(super) the corresponding function on it's parent.
/// CHILD
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button #click="iExist(', but was called from child')">Click Me</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String,
},
methods: {
iExist(add) {
this.$parent.iExist(add);
},
},
};
</script>
The question: Is there something intrinsically bad in this approach?
(I imagine this could be problematic if the app architecture could change, but it’s hard to imagine that I would for some reason need a menu without an underlying document.)
Yes, this is bad design. Parents can be aware of children, children shouldn't be aware of parents. A child could be tested in isolation, or be nested inside wrapper component that doesn't have this method.
As another answer suggests, a way to access a parent is to use $parent property. This part was borrowed in Vue from AngularJS 1.x, accessing it was considered a bad practice even then.
This is generally achieved by providing a callback from a parent that does exactly a desired thing, without allowing to access the whole instance and break the encapsulation. It's unnecessary to explicitly define callback function in Vue because this is naturally provided by Vue template syntax:
In a parent:
<child #save="save()">
In a child:
<button #click="$emit('save')">
In case of deeply nested components the event can be passed through them to a parent.
So, I'm attempting to create a Gutenburg style blog, I'm working on a block creation method. I should caveat - this is entirely my method, if this is the wrong method - great, let me know, but please let me know the correct way! :)
Back to the question. In my project I have the following component.
baseComponent.vue:
<template>
<component v-for="contentBlock in contentBlocks" v-bind:is="contentBlocks.blockComponent" v-bind:key="contentBlock.id" transition="fade" transition-mode="out-in"></component>
</template>
<script>
import CodeBlockComponent from './codeBlockComponent';
export default {
name: 'BaseComponent',
components: {
CodeBlockComponent <!-- Corresponds to the name given in './codeBlockComponent'
},
data: () => ({
contentBlocks: []
})
watch: {
contentBlocks () {
console.log(this.contentBlocks)
}
},
methods: {
addCodeBlock () {
console.log('Code Block Added!')
this.contentBlocks.push({ 'id': this.contentBlocks.length + 1, 'blockType': 'code', 'blockComponent': 'CodeBlockComponent', 'content': '' })
},
addQuoteBlock () {
console.log('Quote Block Added!')
this.contentBlocks.push({ 'id': this.contentBlocks.length + 1, 'blockType': 'quote', 'content': '' })
}
}
}
</script>
N.B. Where above I have stripped the complexity from my template.
Within this same baseComponent I also have buttons which add blocks to the contentBlocks array, where my watch method is definitely finding blocks when added to this array (see below for a screenshot of the console output):
So, everything seems to be going ok - I'm now ready to add block Components. I add the first one, and I receive the following error in the console:
vue.runtime.esm.js?2b0e:587 [Vue warn]: Property or method "CodeBlockComponent" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I took one look at the documentations where it advised to head to, which was here: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties
An example of the CodeBlockComponent.vue:
<template>
<div>
<p>HELLO WORLD FROM THE CODE BLOCK!!</p>
</div>
</template>
<script>
export default {
name: 'CodeBlockComponent',
data: () => ({
}),
computed: {
},
watch: {
},
methods: {
}
}
</script>
I took one look, and I'll be 100% honest - I don't quite understand what it is telling me to do...I feel like I have declared a reactive property? Any advise or pointers anyone can give me would be greatly appreciated!
I'm assuming I can't simply import CodeBlockComponent from './codeBlockComponent'; for this sort of dynamic rendering of components? (But, I really don't know at this point)...
I think what you described should work just fine in case if you've imported and declared in
components: { ... }
all possible names which could be found in each contentBlock.blockComponent. Notice those components must have their name: <String> exactly the same as in your contentBlock.blockComponent. I can't see in your example this prop for item from addQuoteBlock by the way.
You also provided link on documentation, but it's about props, which works just fine in your example. Recheck section about dynamic components, maybe it will help: https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components (notice links on fiddles)
One more thing to check: does your component in baseComponent.vue wrapped with some ? Component must have single root element. Component with v-for probably won't go.
I have a menu of topics (e.g. "About us", "Support") and I want to be able to tell what topic is active right now. When a topic is clicked it will become the active one. Think of it as a substitute for a router that will set an active route. This is not possible in my case since I'm working in SharePoint 2010... in a content editor. So I made the decision to have an activeTopic variable in my Vue instance.
The Vue instance
new Vue({
el: '#app',
data: {
activeTopic: '',
}
});
This is because the activeTopic has to update another component and show some data based on the active topic. Like a router showing a child route.
The topic component
Vue.component('topic', {
props: ["title"],
methods: {
setActiveTopic: function (title) {
activeTopic = title;
},
isActiveTopic: function (title) {
return activeTopic == title;
}
},
template: `
<div v-bind:class="{active: isActiveTopic(title)}" v-on:click="setActiveTopic(title)">
<p v-text="title"></p>
</div>
`
});
The setActiveTopic function works as it should when I click it; it updates the activeTopic. But the isActiveTopic function gives me this error:
Error in render: "ReferenceError: activeTopic is not defined"
What I don't understand is that I can edit the activeTopic variable but I can't make a comparison? I've tried setting a default value but it still says it is undefined.
activeTopic should be assigned to the Vue component by setting and reading this.activeTopic. As is, you have two independent activeTopic variables scoped within each of your component methods.
You'll also want to include activeTopic in the component's data block, rather than on the Vue object itself, since it's specific to the component.
(If there's a reason to put that variable on the Vue object you would either want to pass it down to the component as a prop, or else access it directly as Vue.activeTopic instead of this.activeTopic. I'm honestly not certain whether Vue would treat that last structure as a reactive value within components -- I've never had a reason to try that, and can't think of a situation where that'd be a reasonable architecture.)
Vue.component('topic', {
props: ["title"],
data() { return {
activeTopic: ''
}},
methods: {
setActiveTopic(title) {
this.activeTopic = title;
},
isActiveTopic(title) {
return this.activeTopic == title;
}
},
template: `
<div v-bind:class="{active: isActiveTopic(title)}" v-on:click="setActiveTopic(title)">
<p v-text="title"></p>
</div>
`
});
I am new to Vue, and have seen data being defined as an object in several examples.
But apparently, as per the documentation, you have to use data as a function that returns an object.
This is to ensure that all instances will have it's own version of the data object, and is not shared with all instances.
Instead of
data: {
count: 0
}
use
data: function () {
return {
count: 0
}
}
However I still don't know why in one of my component, data as an object worked, and in a child component with it's own data as an object, I got the error count is undefined;
I am assuming, the root element, (defined by new Vue({}) has to be a singleton, and is not intended to have several instances.
And since components, can have instances, the data in that needs to be defined as a function.
I am wondering that what is a proper way to deal with props in Vue.js if the prop is from the response of backend?
Ok, let's say the child component has a prop called person. a name is in person object.
<template>
{{ person.name }}
<template>
<script>
export default {
name: 'ChildComponent',
props:['person'],
created(){
this.getName();
},
data(){return{name:''}},
methods:{
getName(){
this.name = this.person.name;
}
}
</script>
The parent component is like this
<template>
<ChildComponent :person="person"></ChildComponent>
<template>
<script>
export default {
name: 'ParentComponent',
created(){
this.getPerson();
}
data(){
return {
person: null
}
},
methods:{getPerson(){
// send request to server or api then update name
sendrequest().then(person => { this.person = person});
}}
</script>
At first, before the get the response, there will be a warning can't get name from person.
I know 2 methods to handle this:
<ChildComponent :person="person" v-if="person"></ChildComponent>
watch person prop in, every time the person is changed, rerun the getName() method in childcomponent or set name as a computed attribute.
So here is the question again, are they the proper way to handle this? Is there still some other methods like using Vuex?
Thx
It really depends on your use case, if you don't want to show the person until it is ready then v-if seems like the right way to go.
If you want to display something until the person object is ready you have 2 options:
Give person a default name, or init it with an empty name
show a loading component until person is ready
This is not vue warning. Basically this is javascript error TypeError: Cannot read property 'name' of null, which vue may show you as warning. Your 1 variant is okey. Same as <div v-if="person">{{person.name}}</div> in your child. Also you may fill your person with some thing like
return {
person: {name: 'loading...'}
}
and in your parent
mounted: function () {
// your person will be loaded when component mounted
this.getPerson()
}