I am new to Vue and am trying to use $refs to grab some elements in the DOM from a sibling component (for very basic purposes, just to get their heights, etc.), and I'm doing so in a computed.
No matter what I try, this.$root.$refs either always comes back undefined or as an empty object, and I don't know what I'm doing wrong.
In a parent component, I have:
<template>
<ComponentA />
<ComponentB />
</template>
In component A I have:
<template>
<div id="user-nav">
<div ref="nav-container">
<slot />
</div>
</div>
</template>
I just try to see if I can access this in ComponentB by console logging
console.log(this.$root.$refs);
in that component's mounted function.
But I keep getting an empty object.
Can you just not access things across sibling components like this???
You might have already solved this, but just to answer this Q:
I had a similar problem and it seems that this happens because the function is being called before Vue has had time to replace the root DOM with its own version. What you can do to fix this is to create a mounted life-cycle hook and call the function there.
const vm = new Vue({
el: '#app',
data: {
sayHi: 'Hello World',
},
mounted: function() { // The hook. Here, Vue has already worked its magic.
console.log('YOUR ELEMENT ----> ', this.doSomething())
},
methods: {
doSomething: function() {
return this.$refs['nav-container']
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<span ref="nav-container">{{ sayHi }}</span>
</div>
I found the solution here.
I had the same problem. I was using this.$refs in computed property. I just got rid of computed properties and used methods and everything started to work properly because $refs are not reactive as mentioned for example here and here
This might not be an answer to your particular problem but I discovered that another reason for $refs being empty is that the component on which it is defined has not created/mounted yet. Probably due to the lazy rendering Vue uses.
I had a set of tabs, one of which was the one I had a "ref=" in, and if I did not visit that tab then $refs was empty. But íf I first visited the tab then the ref was set up correctly.
You can do it, the problem you have is that you don't actually have any refs on your parent component.
I do not recommend doing this anyway either use vuex, an eventbus or $emit
Vue.component('componentA', {
template: '<div><span ref="ref-inside-a">You\'re in A!</span></div>',
methods:{
log(){
console.log('Hello from a')
}
}
})
Vue.component('componentB', {
props: ['ball'],
template: '<div><span>This is B!</span></div>',
mounted() {
this.$root.$refs['a'].log()
console.log(this.$root.$refs['a'].$refs['ref-inside-a'])
}
})
new Vue({
el: "#app",
mounted() {
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component-a ref="a"></component-a>
<component-b ref="b"></component-b>
</div>
I had the same problem but the cause was another one:
I had a v-if condition on the referenced element. So at the time of mounting the element was not rendered because the condition was still false.
After 3 hours of looking for the cause for the problem, it was so obvious.
<template>
<sub-component v-if="showCondition" ref="componentName">
<div>someContent</div>
<div>someMoreContent</div>
</sub-component>
</template>
<script lang="ts">
export default class MyComponent extends Vue {
private showCondition = false; // set to true after a REST request
mounted() {
console.log(this.$refs); // this was empty
}
}
</script>
This problem can be solved by moving the v-if to an additional template element inside the referenced component.
<template>
<sub-component ref="componentName">
<template v-if="showCondition">
<div>someContent</div>
<div>someMoreContent</div>
</template>
</sub-component>
</template>
<script lang="ts">
export default class MyComponent extends Vue {
mounted() {
console.log(this.$refs); // now 'componentName' is available
}
}
</script>
In my specific case the
this.$refs
part referenced to several components. Accessing a specific component by reference was possible:
this.$refs[componentName]
To access a specific field of the component above I had to select the first observable:
this.$refs[componentName][0].someFieldToBeSelected -> this works
this.$refs[componentName].someFieldToBeSelected -> this fails
.
Related
I want a child component to reload everytime the object, which i transfer to the child as a prop, changes. I read that VueJs can not detect a change in an Object. So far so good, I came up with the following Idea:
Everytime my Object changes, I perform a change in a normal variable which I also transfer via a prop to the child. My idea was it to "force" a rerendering of the child component through the change of the normal variable. But it seems not to work and I don't understand why it doesn't work.
Parent File:
<template>
<compare-value :ocean="ocean" :update="updateComponent" v-if="ocean.length > 0"></compare-value>
</template>
<script>
import CompareValue from '#/views/compare/CompareValue'
...
components: {
CompareValue
},
...
updateComponent: 0,
...
methods: {
reloadCompnent() {
this.updateComponent += 1;
},
getData() {
this.ocean.push({
userid: userId,
data1: this.result.data_john,
data2: this.result.data_mike,
data3: this.result.data_meika,
data4: this.result.data_slivina,
})
this.reloadCompnent() //Force the reload of the component
}
}
</script>
Child File:
<template>
{{ update }}
</template>
<script>
...
props: [
'ocean',
'update'
],
...
</script>
As far as I understood, a change of a normal variable triggers the component to be reloaded but it seems I oversee something.
Setting an existing Object prop is actually reactive, and so is adding a new object to an array prop. In your example, getData() would cause compare-value to re-render without having to call reloadComponent() (demo).
I read that VueJs can not detect a change in an Object.
You're probably referring to Vue 2's change-detection caveats for objects, which calls out addition or deletion of properties on the object.
Caveat example:
export default {
data: () => ({
myObj: {
foo: 1
}
}),
mounted() {
this.myObj.foo = 2 // reactive
delete this.myObj.foo // deletion NOT reactive
this.myObj.bar = 2 // addition NOT reactive
}
}
But Vue provides a workaround, using Vue.set() (also vm.$set()) to add a new property or Vue.delete() (also vm.$delete()) to delete a property:
export default {
//...
mounted() {
this.$delete(this.myObj, 'foo') // reactive
this.$set(this.myObj, 'bar', 2) // reactive
}
}
thanks for the answers and I tested your suggested answer and I would have worked but I did something else. I just replaced the :update with :key and it worked. After this action the Component is automatically reloaded.
The solution looks exactly like the one i posted in the question just one (importatn) tiny little thing is different. See below.
<template>
<compare-value :ocean="ocean" :key="updateComponent" v-if="ocean.length > 0"></compare-value>
</template>
Thanks and hope it will help others too.
Br
MyMixin.vue has the method beginEdit.
What I'm trying to do is to make onFirstLineClick to call myMixin's beginEdit depending on the value of this.folded.
When I console logged myMixin.beginEdit, it is undefined and not surprisingly myMixin.beginEdit() doesn't work.
Am I missing something needed to use the function? If so, why does beginEdit work perfectly on <span>?
<template>
<div>
<div>
<div
#click="onFirstLineClick"
/>
<span
#click="beginEdit"
/>
</div>
</template>
<script>
import myMixin from './MyMixin';
export default {
name: 'currComponent',
mixins: [myMixin],
data() {
return {
folded: false,
};
},
methods: {
onFirstLineClick(e) {
// myMixin.beginEdit() doesn't work
}
},
};
</script>
The great thing about mixin is that when a component uses a mixin, all options in the mixin will be "mixed" into the component's own options. Which means that inside your component you can call mixin method directly like:
methods: {
onFirstLineClick(e) {
this.beginEdit()
}
},
That is also the reason why you can use beginEdit() method on <span> directly like:
<span #click="beginEdit" />
Please also know that in future if you declare a method in this component with same name as mixin method name beginEdit, then the component's method will take priority and you might see different behaviour. So, make sure to give unique names to mixin methods.
I'm trying to figure out what the default properties (props) were for a child component. In this example, I have two components A and B. B wraps around A and A is passed properties. I'd like to figure out what the default values were for the component A from the component B which wraps around it, most importantly the types specified for the defaults. To be clear, in the mounted function of B I'd like to be able to see the default values and types of A assuming B will always have exactly 1 child component.
I've tried getting the child component using this.$children[0]._props in the mounted lifecycle hook, but those properties are the ones set. I've tried getting the properties earlier in the Vue lifecycle (like created, beforeCreate, beforeMount etc.) except they don't seem to exist until mounting. I've also inspected the this.$children[0] object using the browser console, and haven't found any default values for the props (only getter functions which retrieve the override defaults). I'm willing to pass extra data to B in order to get the default properties, but would prefer not to (since it seems redundant, i.e. I should know what the component "origin" was by looking at the this.$children[0] object).
Minimal example is located here: https://codepen.io/maxschommer/pen/wvaqGjx
I've included the HTML and JS below for quick reference.
JS:
Vue.component('A', {
name: "A",
props: {
prop1: {
type: String,
default: "The First Default Value"
},
prop2: {
type: String,
default: 'The Second Default Value'
}
},
template: `<div class="A">
<h1>A Prop 1: {{ prop1 }} </h1>
<h1>A Prop 2: {{ prop2 }} </h1>
</div>`
});
Vue.component('B', {
name: "B",
mounted: function() {
this.$children[0];
// I'd like to find some way to get the default properties
// of the child of this component (A) here (or in computed, etc.). Instead
// this gives the values which were set.
alert(JSON.stringify(this.$children[0]._props));
},
template:
`<div><slot></slot></div>`});
var parent = new Vue({
el: "#app",
template:
`<div class=templateField>
<B>
<A prop1="Overriding" prop2="Defaults"></A>
</B>
</div>`
});
HTML:
<div id="app">
</div>
PS: I'm a bit confused about the difference between components and elements when refering to Vue (I believe components are JS objects and elements are when they are rendered to html) so please correct my terminology if I'm getting it wrong.
You can access the original options object (the object you give Vue to construct component instances) from this.$options, so
mounted() {
const propDfns = this.$options.__proto__.props
const propTypes = Object.values(propDfns).map(p => p.type.name)
console.log(propTypes)
},
Components are not only JS objects. they are mixture of js, html or template and css
I watched a video to understand how to use event bus to communicate between siblings, and in the video there was a parent with some data that was sent to the childs as a prop, then a method in one of the childs modified that prop and used an event bus to send it to the other child.
I thought, whats the point of the prop? Why cant i just use the siblings own data? And thats what i did:
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
export const bus = new Vue() //Event Bus
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<ComponenteA></ComponenteA>
<ComponenteB></ComponenteB>
</div>
</template>
<script>
import ComponenteA from './components/ComponenteA.vue'
import ComponenteB from './components/ComponenteB.vue'
export default {
name: 'App',
components:{
ComponenteA,
ComponenteB
}
}
</script>
ComponentA.vue
<template>
<div>
<h1 #click="changeTitle">Componente A</h1>
</div>
</template>
<script>
import { bus } from '../main'
export default {
name: 'ComponenteA',
data(){
return{
title: ''
}
},
methods:{
changeTitle(){
this.title = 'Title emitted from A a B'
bus.$emit('titleChanged',this.title)
}
}
}
</script>
ComponentB.vue
<template>
<div>
<h1>Componente B -> {{title}}</h1>
</div>
</template>
<script>
import { bus } from '../main'
export default {
name: 'ComponenteB',
data(){
return{
title: ''
}
},
created(){
bus.$on('titleChanged', (payload) =>{
this.title = payload
})
}
}
</script>
Is there anything wrong with my code? Is there a reason for using the parent data that im failing to see?
The reason that the data should start in the parent and get passed down to the children is because of a couple things:
The reactivity system in Vue.js is built around having your data in a single place (a "single source of truth"), then passing that data to wherever it is needed. Now, if only a single component needs the data, you'll just store that data on the component. But the data is needed in multiple components, you'll want to store it in a parent and then pass it to the children. This becomes particularly obvious if you need to start using Vuex.
If for some reason you need to change, say, the name of the data (e.g., pageTitle instead of title), it becomes far easier to trace where the data came from if it always comes from a parent. Relying on the event bus across siblings can become rather brittle as the project grows larger.
So, in your case, the title should really exist in the data of your App.vue component. Each of the children would receive it as a prop. Then, if the title was changed, $emit() an event that App.vue is listening for. That event would change the data in App.vue.
Take a look at this question for more details:
vuejs update parent data from child component
I have an application which has Vue components as children.
The components pass back data to the parent via a this.$emit (numberchnaged below), which is caught at the parent level by a v-on (or #) directive, which in turns triggers a method.
This method then updates a data() property of the parent:
<template>
(...)
<Users #numberchanged="doNumCh"></Users>
(...)
</template>
<script>
(...)
export default {
components: {
Users
},
data() {
return {
u: "hello"
}
},
methods: {
doNumCh(value) {
this.u = value
}
}
}
</script>
This solution works but is quite verbose for just updating this.u with what <Users> sent back.
Is there a way to make the update right in the <Users> tag, something like
<Users #numberchanged="u=theValueReturedByUsers"></Users>
My problem is that I do not know how to extract theValueReturedByUsers, I only get hold of it in the method as value (in my example above).
Functionally, you're looking to have v-model behavior on your component. Vue provides for that. So you can say
<template>
(...)
<Users v-model="u"></Users>
(...)
</template>
which is a tidy view, as long as your Users component (side note: you should always have a hyphen in custom component names) takes the value parameter and $emits the input event.
See also v-bind.sync to work with props other than value.
The payload is reachable via $event.
For the code above, the solution would therefore be
<Users #numberchanged="u=$event"></Users>
You can do it like this (without write a method in parent) with using the variable $event which contains the value returned (Object or literal variable) from child component:
<users #numberchanged="{u=$event}"></users>