VueJS template ref undefined with multiple divs? - javascript

I have a simple vue component, what I try is to get the DOM element from the template. Here is the component, which WORKS fine:
<template>
<div ref="cool">COOL!</div>
</template>
<script>
export default {
data() {
return {
blabla: 1
}
},
mounted() {
console.log("MOUNTED:");
var cool = this.$refs.cool;
console.log(cool); //works!
}
}
</script>
And now the strange thing: I add another element to my template, doesn't matter which one..eg. a new div:
<template>
<div ref="cool">COOL!</div>
<div>I am new</div>
</template>
Now the console.log in mounted() returns undefined.

Vue requires you to have one top level element, so you need to wrap the whole thing in a div:
<template>
<div>
<div ref="cool">COOL!</div>
<div>I am new</div>
</div>
</template>
Here's the JSFiddle: https://jsfiddle.net/nrtkr6cL/

Related

Vue3 pass the data from Parent to Child

I bumped into this error when trying to pass the data from parent to child.
vue#next:1616 [Vue warn]: Extraneous non-props attributes (parentdata) were passed to component but could not be automatically inherited because component renders fragment or text root nodes.
at <Child parentdata="Data From Parent" >
at <App>
My source code is here below, just simply pass the data to Child from Parent.
I guess this is related with text root nodes? if so how can I fix this?
<div id="app">
<child parentData="Data From Parent"></child>
</div>
<template id="child_comp">
Child component test [[ parentData]]
</template>
<script>
const Child = {
props:['parentData'],
template: '#child_comp',
data (){
return {
}
},
mounted() {
console.log(this.parentData);
},
delimiters: ['[[', ']]']
}
Vue.createApp({
components:{
"child":Child,
},
methods:{
}
}).mount('#app')
</script>
Since your template is placed directly in the HTML, the parentData cannot be bound and generates the warning.
You should bind it with kebab-case like this
<child parent-data="Data From Parent" ></child>
Check the Vue Docs DOM Template Parsing Caveats for details.
BTW, also good to know:
The warning comes while you component does not have a single root node.
To prevent the warning you can put a <div> around your component's content, like this:
<div>Child component test : [[ parentData ]]<br/></div>
Or use inheritAttrs: false to suppress it.
And here is a great answer clarifying the problem with the warning.
Example
Here is the playground showing it. The first parentData="Data From Parent" generates the warning and does not work. The second parentData="Data From Parent" works.
const Child = {
props:['parentData'],
template: '#child_comp',
mounted() {
console.log(this.parentData);
},
delimiters: ['[[', ']]']
}
Vue.createApp({
components:{
"child": Child,
}
}).mount('#app')
<div id="app">
<child parentData="Data From Parent" ></child>
<child parent-data="Data From Parent" ></child>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
<script type="text/x-template" id="my-component">
</script>
<template id="child_comp">
Child component test : [[ parentData ]]<br/>
</template>

Vue JS Non prop attributes and Disabling Attribute Inheritance

I have read the docs more than 5 times and I still can't understand what is Disabling Attribute Inheritance is used for and I don't understand the example given.
I understand how props works, it is passing data from parent component to child component.
Parent.vue
<template>
<div id="app">
<Child :num="num"></Child>
</div>
</template>
<script>
import Child from "#/components/Child";
export default {
name: "App",
components: {
Child
},
data() {
return {
num: 42
};
}
};
</script>
Child.vue
<template>
<div>
<h2>Child</h2>
<h4>num is {{num}}</h4>
<div id="total">
<h4>total is {{total}}</h4>
</div>
</div>
</template>
<script>
export default {
name: "Child",
props: {
num: {
type: Number,
default: 100
}
},
data() {
return {
};
},
computed: {
total() {
return this.num + 20;
}
}
};
</script>
This will output num is 42 and total is 62. Which I understand perfectly.
However when it comes to Disabling Attribute Inheritance section , by base component, I assume they are referring to the child components.
"This pattern allows you to use base components more like raw HTML elements, without having to care about which element is actually at its root:"
What does this even mean ? Does this mean that parent don't have to pass on props anymore to the child and the child can automatically pick up the props from it's parent which frankly doesn't make sense or there is no parent component altogether, they are only using child component?
From their example, props: ['label' , 'value'] , how is the child component receiving these two props without the parent component passing these props to them?
I would be very thankful if someone can use the parent and vue component analogy above to provide a simple example of what does this even mean.
Thank you.
If you put a random html attribute onto the component, it will appear on the rendered component. If you disable inheritance, it won't.
<my-component dorkus="whatever"></my-component>
when rendered, perhaps expands to
<div dorkus="whatever"> ... stuff ... </div>
but if you set inheritAttrs: false, it looks like
<div> ... stuff ... </div>
that's all there is to it.
dorkus="whatever" is still stored in the $attrs object, should we want to do something else with it.
Disabling Attribute Inheritance is used when you don't want html attributes assigned to the component to be passed to the root element in a component.
Parent.vue
<template>
<div id="app">
<Child title="I am the child"></Child>
</div>
</template>
<script>
import Child from "#/components/Child";
export default {
name: "App",
components: {
Child
}
};
</script>
Child.vue
<template>
<div>
<h2 v-bind="$attrs">Child</h2> <!-- This h2 will inherit all the html attributes -->
</div>
</template>
<script>
export default {
inheritAttrs: false, // This is what disables attribute inheritance
name: "Child"
};
</script>
inheritAttrs: false prevents the root component div from inheriting the title assigned to the Child component in Parent.vue.
Now notice how I use v-bind="$attrs" on the h2 in Child.vue. $attrs contains all the attributes that would have been assigned to the root div element. Now the attributes are assigned to the h2 and not the div.
inheritAttrs: false doesn't affect props or vue attributes, it affects normal html attributes. (It also doesn't affect style and class attributes)
Base Components
By "Base Components" the docs at vuejs.org means components like buttons, inputs, etc.
Using inheritAttrs: false is really useful for input components:
<template>
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
</template>
<script>
export default {
name: 'base-input',
inheritAttrs: false,
props: ['label', 'value'],
}
</script>
This allows you to do:
<base-input
v-model="username"
required
placeholder="Enter your username"
></base-input>
Which means that the placeholder and required attributes are applied to the input in the component and not the label.
Something to add
If you have multiple non-prop attributes and multiple root nodes in a component, and the non-prop attributes are meant for different elements, you can bind them like this
<mycomponent
data-haha="haha"
data-hehe="hehe">
</mycomponent>
// in the component
template: `
<p v-bind:data-yoyo="$attrs['data-haha']">only data-haha is used, and attribute data-haha renamed to data-yoyo</p>
<p v-bind="$attrs">all attributes here</p>
`
Rendered HTML
<p data-yoyo="haha">only data-haha is used, and attribute data-haha renamed to data-yoyo</p>
<p data-haha="haha" data-hehe="hehe">all attributes here</p>

Change a property's value in one component from within another component

I'm trying to wrap my head around hoe Vue.js works, reading lots of documents and tutorials and taking some pluralsight classes. I have a very basic website UI up and running. Here's the App.vue (which I'm using kinda as a master page).
(To make reading this easier and faster, look for this comment: This is the part you should pay attention to)...
<template>
<div id="app">
<div>
<div>
<CommandBar />
</div>
<div>
<Navigation />
</div>
</div>
<div id="lowerContent">
<!-- This is the part you should pay attention to -->
<template v-if="showLeftContent">
<div id="leftPane">
<div id="leftContent">
<router-view name="LeftSideBar"></router-view>
</div>
</div>
</template>
<!-- // This is the part you should pay attention to -->
<div id="mainPane">
<div id="mainContent">
<router-view name="MainContent"></router-view>
</div>
</div>
</div>
</div>
</template>
And then in the same App.vue file, here's the script portion
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import CommandBar from './components/CommandBar.vue';
import Navigation from './components/Navigation.vue';
#Component({
components: {
CommandBar,
Navigation,
}
})
export default class App extends Vue {
data() {
return {
showLeftContent: true // <--- This is the part you should pay attention to
}
}
}
</script>
Ok, so the idea is, one some pages I want to show a left sidebar, but on other pages I don't. That's why that div is wrapped in <template v-if="showLeftContent">.
Then with the named <router-view>'s I can control which components get loaded into them in the `router\index.ts\ file. The routes look like this:
{
path: '/home',
name: 'Home',
components: {
default: Home,
MainContent: Home, // load the Home compliment the main content
LeftSideBar: UserSearch // load the UserSearch component in the left side bar area
}
},
So far so good! But here's the kicker. Some pages won't have a left side bar, and on those pages, I want to change showLeftContent from true to false. That's the part I can't figure out.
Let's say we have a "Notes" component that looks like this.
<template>
<div class="notes">
Notes
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
Obviously, I'm not handling showLeftContent properly here. It would seem as if the properties in data are scoped only to that component, which I understand. I'm just not finding anything on how I can set a data property in the App component and then change it in a child component when that child is loaded through a router-view.
Thanks!
EDIT:
I changed the script section of the Notes component from:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
data() {
return {
showLeftContent: false // DOES NOT WORK
}
}
}
</script>
to:
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component
export default class Notes extends Vue {
mounted() {
this.$root.$data.showLeftContent = false;
}
}
</script>
And while that didn't cause any compile or runtime errors, it also didn't have the desired effect. On Notes, the left side bar still shows.
EDIT 2:
If I put an alert in the script section of the Notes component:
export default class Notes extends Vue {
mounted() {
alert(this.$root.$data.showLeftContent);
//this.$root.$data.showLeftContent = false;
}
}
The alert does not pop until I click on "Notes" in the navigation. But, the value is "undefined".
EDIT 3:
Struggling with the syntax here (keep in mind this is TypeScript, which I don't know very well!!)
Edit 4:
Inching along!
export default class App extends Vue {
data() {
return {
showLeftContent: true
}
}
leftContent(value: boolean) {
alert('clicked');
this.$root.$emit('left-content', value);
}
}
This does not result in any errors, but it also doesn't work. The event never gets fired. I'm going to try putting it in the Navigation component and see if that works.
As it says on #lukebearden answer you can use the emit event to pass true/false to the main App component on router-link click.
Assuming your Navigation component looks like below, you can do something like that:
#Navigation.vue
<template>
<div>
<router-link to="/home" #click.native="leftContent(true)">Home</router-link> -
<router-link to="/notes" #click.native="leftContent(false)">Notes</router-link>
</div>
</template>
<script>
export default {
methods: {
leftContent(value) {
this.$emit('left-content', value)
}
}
}
</script>
And in your main App you listen the emit on Navigation:
<template>
<div id="app">
<div>
<Navigation #left-content="leftContent" />
</div>
<div id="lowerContent">
<template v-if="showLeftContent">
//...
</template>
<div id="mainPane">
//...
</div>
</div>
</div>
</template>
<script>
//...
data() {
return {
showLeftContent: true
}
},
methods: {
leftContent(value) {
this.showLeftContent = value
}
}
};
</script>
A basic approach in a parent-child component relationship is to emit events from the child and then listen and handle that event in the parent component.
However, I'm not sure that approach works when working with the router-view. This person solved it by watching the $route attribute for changes. https://forum.vuejs.org/t/emitting-events-from-vue-router/10136/6
You might also want to look into creating a simple event bus using a vue instance, or using vuex.
If you'd like to access the data property (or props, options etc) of the root instance, you can use this.$root.$data. (Check Vue Guide: Handling Edge)
For your codes, you can change this.$root.$data.showLeftContent to true/false in the hook=mounted of other Components, then when Vue creates instances for those components, it will show/hide the left side panel relevantly.
Below is one demo:
Vue.config.productionTip = false
Vue.component('child', {
template: `<div :style="{'background-color':color}" style="padding: 10px">
Reach to root: <button #click="changeRootData()">Click me!</button>
<hr>
<slot></slot>
</div>`,
props: ['color'],
methods: {
changeRootData() {
this.$root.$data.testValue += ' :) '
}
}
})
new Vue({
el: '#app',
data() {
return {
testValue: 'Puss In Boots'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h2>{{testValue}}</h2>
<child color="red"><child color="gray"><child color="green"></child></child></child>
</div>

How to pass params from div to single page component in Vue.js?

I have such code on different pages:
<div id="contact-us" class="section md-padding bg-grey">
<div id="contact"></div>
<script src="/dist/build.js"></script>
</div>
I have main.js:
import Vue from 'vue'
import Contact from './Contact.vue'
new Vue({
el: '#contact',
render: h => h(Contact)
})
And Contact.vue with a template
I want to know from which page component was used. So I need to pass param from div like <div id="contact" page="main"></div> . How can I implement this?
How to pass params from div to single page component in Vue.js?
You can't pass params from a div since it's a html tag and a not custom component, you should define your own component that accepts the properties you want to pass.
So first you should define your component and define the property is allow to receive, then you use your component, take a look to the below example, and you may find more information about passing props here.
Vue.component('your-component', {
props: ['property'],
template: '<h3>{{ property }}</h3>'
})
new Vue({
el: '#app'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<your-component :property="'Hello props'" />
</div>
Example using Single File Component structure.
Parent component:
<template>
<ChildComponent :property="propValue" />
</template>
<script>
import childComponent from './childComponent.vue';
export default {
components: {
ChildComponent: childComponent
},
data() {
return {
propValue: 'Hello prop'
}
}
}
</script>
Children component:
<template>
<h3>{{ property }}</h3>
</template>
<script>
export default {
props: ['property'] // You can add more properties separeted by commas
}
</script>

Is it possible to pass a component as props and use it in a child Component in Vue?

In a Vue 2.0 app, let's say we have components A, B and C.
A declares, registers and uses B
Is it possible to pass C from A to B?
Something like this:
<template>
<div class="A">
<B :child_component="C" />
</div>
</template>
And use C in B somehow.
<template>
<div class="B">
<C>Something else</C>
</div>
</template>
The motivation: I want to create a generic component B that is used in A but receives from A its child C. Actually A will use B several times passing different 'C's to it.
If this approach is not correct, what is the proper way of doing it in Vue?
Answering #Saurabh
Instead of passing as props, I tried the suggestion inside B.
<!-- this is where I Call the dynamic component in B -->
<component :is="child_component"></component>
//this is what I did in B js
components: {
equip: Equipment
},
data () {
return {
child_component: 'equip',
_list: []
}
}
Basically I'm trying to render Equipment, but the dynamic way
I get 3 errors in console and a blank page
[Vue warn]: Error when rendering component at /home/victor/projetos/tokaai/public/src/components/EquipmentFormItem.vue:
Uncaught TypeError: Cannot read property 'name' of undefined
TypeError: Cannot read property 'setAttribute' of undefined
Apparently I'm doing something wrong
Summing up:
<!-- Component A -->
<template>
<div class="A">
<B>
<component :is="child_component"></component>
</B>
</div>
</template>
<script>
import B from './B.vue';
import Equipment from './Equipment.vue';
export default {
name: 'A',
components: { B, Equipment },
data() {
return { child_component: 'equipment' };
}
};
</script>
<!-- Component B -->
<template>
<div class="B">
<h1>Some content</h1>
<slot></slot> <!-- Component C will appear here -->
</div>
</template>
You can use special attribute is for doing this kind of thing. Example of dynamic component and its usage can be found here.
You can use the same mount point and dynamically switch between multiple components using the reserved element and dynamically bind to its is attribute.
Here's how is can be used with either an imported component or one passed as a prop:
<template>
<div class="B">
<component :is="myImportedComponent">Something</component>
--- or ---
<component :is="myPassedComponent">Something else</component>
</div>
</template>
<script>
import myImportedComponent from "#/components/SomeComponent.vue"
export default {
props: {
myPassedComponent: Object
},
components: {
myImportedComponent
},
}
</script>
Here's solution to forward custom component through props of another component
:is is special attribute and it will be used to replace your actual component and it will be ignored if you try to use it as a prop in your component. Luckily you can use something else like el and then forward this to component like so:
<template>
<div>
<component :is="el">
<slot />
</component>
</div>
</template>
<script>
export default {
name: 'RenderDynamicChild',
props: {
el: {
type: [String, Object],
default: 'div',
},
},
}
</script>
Any valid element you use in el attribute will be used as a child component. It can be html or reference to your custom component or div by default as specified in component declaration.
Passing custom component to prop is little bit tricky. One would assume you declare in a components property of parent component and then use it for el attribute but this doesn't work. Instead you need to have your dynamic component in data or computed property so you can use it in a template as a prop. Also note AnotherComponent doesn't need to be declared in components property.
<template>
<RenderDynamicChild :el="DynamicComponent">
Hello Vue!
</RenderDynamicChild>
</template>
<script>
import RenderDynamicChild from './DynamicChild';
import AnotherComponent from './AnotherComponent';
export default {
name: "ParentComponent",
components: { DynamicChild },
data() {
return {
DynamicComponent: AnotherComponent,
};
},
};
</script>
Using computed property for your dynamic component allows you to switch between components easily:
<script>
import DynamicChild from './DynamicChild';
import AnotherComponent from './AnotherComponent';
export default {
name: "ParentComponent",
components: { DynamicChild },
data() { return { count: 0 } },
computed: {
DynamicComponent() {
return this.count % 2 > 1 ? AnotherComponent : 'article';
},
},
};
</script>
Increase this.count to alternate between AnotherComponent and simple article html element.
Maybe it's too late to answer this question. But I think it could help others with this same issue.
I've been looking for a way to pass components throw others in vue, but it looks that VUE3 have a approach for that using named slots:
Here it's the documentation about that:
https://v3.vuejs.org/guide/component-slots.html#named-slots
Basically you can have:
<template>
<div class="A">
<slot name="ComponentC"></slot> <!-- Here will be rendered your ComponentC -->
</div>
<div class="A">
<slot name="ComponentD"></slot> <!-- Here will be rendered your ComponentD -->
</div>
<div class="A">
<slot></slot> <!-- This is going to be children components -->
</div>
</template>
And from your B component
<template>
<div class="B">
<A>
<template v-slot:ComponentC>
<h1>Title of ComponentC </h1>
</template>
<template v-slot:ComponentD>
<h1>Title of ComponentD </h1>
</template>
<template v-slot:default>
<h1>Title of child component </h1>
</template>
</A>
</div>
</template>
If you would like to use another component within your functional component you can do the following:
<script>
import Vue from 'vue'
import childComponent from './childComponent'
Vue.component('child-component')
export default {}
</script>
<template functional>
<div>
<child-component/>
</div>
</template>
Reference:
https://github.com/vuejs/vue/issues/7492#issue-290242300
If you mean Dynamically importing a component in a parent component, so yes, you can do that in Vue3 using:
<component :is="child_component" />
but to render "child_component" itself dynamically, you can use
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
Let me give you an example:
let's say you have several multiple child components (ChildA, ChildB, ChildC) that you want to load dynamically based on what you pass to the parent component (Parent), so the Parent component will be something like this:
Parent
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
const props = defineProps<{
childComponent?: string;
}>();
const AsyncComp = defineAsyncComponent(() =>
import(`./${props.childComponent}.vue`)
)
</script>
<template>
<component :is="AsyncComp"/>
</template>
and then you can call the Parent component dynamically wherever you want like this:
<Parent :childComponent="child-a"/>
<Parent :childComponent="child-b"/>
<Parent :childComponent="child-c"/>
For a better explanation, you can check this article:
https://medium.com/#pratikpatel_60309/dynamic-importing-component-templates-with-vue-js-78d2167db1e7

Categories

Resources