I'm new to Vue, and am trying to build up a set of business components that rely on other lower-level components, and pass properties to them. None of the usual data-binding v-bind:propName="xxx" or :propName="xxx" seem to work from inside a component template however, and all the documentation treats prop passing from a top-level HTML page instead of from one component to another.
For example, with the below I have a lower-level component called "exchange-panel" which takes a "title" property. I want my higher-level component to use that generic exchange-panel and pass in a title to use. I get the following error:
[Vue warn]: Error compiling template:
<exchange-panel :title="The Panel Title">
<h1>test</h1>
</exchange-panel>
- invalid expression: :title="The Panel Title"
found in
---> <OrderbookDetail>
Here is the sample code (also at https://jsfiddle.net/ns1km8fh/
Vue.component("orderbook-detail", {
template:`
<exchange-panel :title="Public order book">
<h1>test</h1>
</exchange-panel>
`
});
Vue.component("exchange-panel", {
template:`
<div class="panel panel-default">
<div class="panel-heading">Title: {{ title }}</div>
<div class="panel-body">
<slot></slot>
</div>
</div>`,
props: ["title"]
})
new Vue({
el: "#exchange-container",
created: function () {
window.Vue = this
}
})
You need to quote Public order book.
<exchange-panel :title="'Public order book'">
The value of a bound attribute is treated as a javascript expression. Javascript strings need to be enclosed in quotes of some kind (single, double, backticks).
Updated fiddle.
As #RoyJ points out, you can also simply not bind it and use it as a normal attribute.
<exchange-panel title="Public order book">
Related
I have a very simple app vue3:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
let currentApp = createApp(App);
(window as any).load(currentApp);
currentApp.mount('#app');
In the index.html file i import a dist lib:
<script src="http://localhost:5173/dist/assets/index.59eda240.js">
The content of dist JS is:
import HelloWorld from './components/HelloWorld.vue'
(window as any).load = (app: any) => {
app.component('hello-world', HelloWorld);
}
If i use a very simple HelloWorld.vue with just one line of text "Hello", all works fine, but if i put some css classes or more complicated component i obtain:
[Vue warn]: Invalid VNode type: Symbol() (symbol) at <HelloWorld>
at <App>
How can i solve this? Or is there another way to load component at runtime?
Thanks in advance.
P.s. I have no problem doing the same thing with Vue
--------- UPDATE MORE INFO ----------
Vue version
3.2.38
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-744fbh?file=src/main.js
Steps to reproduce
The base idea is to have a main app the create a Vue app, and another external lib the load component at runtime.
Open the sample project and show console log to see "Invalid VNode type: Symbol() (symbol)"
What is expected?
I expect to see the components rendered correctly
What is actually happening?
I don't see the component UI, but looking at the console I see the logic code works correctly but we have a warning: Invalid VNode type: Symbol() (symbol) Invalid VNode type: Symbol() (symbol)
System Info
No response
Any additional comments?
This warning appears only in some components, for example, fur a very simple component, it works fine, see here: https://stackblitz.com/edit/vitejs-vite-nueucw?file=src/App.vue.
You can see the source of helloworld component at the top of lib.js file, inside a comment block
The actual problem is that there are multiple Vue library copies per page that interact with each other. This should be avoided, preferably by bundling multiple parts of the application together, so multiple apps would reuse the same modules from vendors chunk. In case this isn't possible or practical, a simple workaround is to use Vue CDN for all applications, either by using window.Vue instead of vue import, or aliasing the import to global variable by means of a bundler.
The root cause is that Vue built-in element are specific to Vue copy in use - Fragment, Teleport, Comment, etc. They are defined with Symbol() to be distinguished from other elements and components and used in compiled component templates. Symbol is used in JavaScript to define unique tokens, Symbol('foo') !== Symbol('foo'). The symbols of built-in elements from one Vue copy mean nothing to another Vue copy, this is the meaning of this error message.
It can be seen in this analyzer that this component template:
<h1 style="background-color:Red; font-size: 44px;">{{ msg }}</h1>
<button #click="msg = 'click';">CLICK</button>
is compiled to this render function:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("h1", { style: {"background-color":"Red","font-size":"44px"} }, _toDisplayString(_ctx.msg), 1 /* TEXT */),
_createElementVNode("button", {
onClick: $event => {_ctx.msg = 'click';}
}, "CLICK", 8 /* PROPS */, ["onClick"])
], 64 /* STABLE_FRAGMENT */))
}
Notice that the use of multiple root elements results in wrapping them in Fragment, which won't be correctly processed by multiple Vue copies, etc.
Change hello world with another component:
<template>
<div class="relative flex items-top justify-center min-h-screen sm:items-center py-4 sm:pt-0">
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<div class="card rounded-lg shadow-lg" style="background-color: yellow;">
<div class="card-body p-0 m-0 pt-2" style="background-color: orange;">
<div class="row border-bottom p-0 m-0 pt-2" style="background-color: brown;">
<div class="bg-secondary">
<h5 class="bg-info">test</h5>
<p>
test
</p>
</div>
</div>
<div>
<div>
test2
</div>
</div>
</div>
</div>
</div>
</div>
</template>
Only one root element, I have the same issue.
could you explain Vue template vs Vue function jsx, what is different of it ? which one is good for use ?
Ex :
I have two components :
Component1.vue
<template>
<div>
<p>{{message}}</p>
</div>
<template>
<script>
export default {
name:'Component1',
data(){
return{
message:'This is component1'
}
},
}
</script>
Component2.vue
export default {
name:'Component2',
data(){
return{
message:'This is component2'
}
},
render(){
return(<p>{this.message}</p>)
}
}
Could I write like component2.vue ? How about performance of both ?
Both versions of writing the component will do the same thing. As far as the performance is considered, there would be no difference. Both are compiled into render function that returns Virtual DOM tree for your component.
The difference is the flavor and syntax of the implementation. Though with Vue, we mostly use templates as they are more readable over JSX, there are situation where JSX is more appropriate to use. For example, consider the case where you are trying to design a dynamic header component where level prop decides which <h1...h6> tag to use:
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
Same thing can be written more elegantly using render function or JSX:
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // tag name
this.$slots.default // array of children
)
},
props: {
level: {
type: Number,
required: true
}
}
});
Also, if you are using TypeScript, JSX syntax will provide you compile-time check for validating props and attributes, though setting that with Vue 2 is quite an hassle. With Vue 3, that is much simpler.
As far as dynamic loading of component is considered, you can use built-in <component /> component with is prop within the template as:
<component v-bind:is="someComponentToRenderDynamically"></component>
So, this brings the same advantages as JSX or direct render function based component. For more documentations see:
Dynamic Components
Render Function & JSX
First of all let's see what are Template syntax and JSX:
JSX: A syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file. Basically, JSX is a JavaScript render function that helps you insert your HTML right into your JavaScript code.
Template syntax: An HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying component instance's data.
Using Vue templates is like using JSX in that they’re both created using JavaScript. The main difference is that Vue templates are syntactically valid HTML that can be parsed by spec-compliant browsers and HTML parsers.
What does it mean?
JSX functions are never used in the actual HTML file, while Vue templates are.
What is the difference? which one is better to use ?
According to the Vue.js documentation, Vue compiles the templates into highly-optimized JavaScript code.
But if you are familiar with Virtual DOM concepts and prefer the raw power of JavaScript, you can also directly write render functions instead of templates, with optional JSX support.
However, do note that they do not enjoy the same level of compile-time optimizations as templates.
So, we can conclude that writing template syntax with Vue is more optimized.
The vue template is much more readable and easier to understand than jsx functions.
It's much easier to save variables / properties and access them using "{{someVariables}}", rather than always telling a vue method to read them
In short, it's better to use the vue template because it's easier to implement dynamic pages with it (and other things).
Also, at this point it's not a very good idea to keep sending html code through methods.
I know this is a common question, but I have been going through my files now so many times without being able to the locate the error.
I am getting this error when I try to route to my components in my navigation menu.
My app.vue file:
<template>
<div id="app">
<Navbar
:nav-links="navLinks"
/>
<router-view/>
</div>
</template>
<script>
import Navbar from '#/components/Navbar'
export default {
components: {
Navbar
},
data: () => ({
navLinks: [
{
text: 'Home',
path: '/home'
},
{
text: 'About',
path: '/about'
},
{
text: 'Contact',
path: '/contact'
}
]
})
}
</script>
My Navbar component (This is where the error happens)
<template>
<nav>
<ul>
<li v-for="{link, index} in navLinks" :key="index"
#mouseover="hover = true"
#mouseleave="hover = false">
<router-link :to="link.path">
{{ link.text }}
</router-link>
</li>
</ul>
</nav>
</template>
<script>
export default {
props: ['navLinks'],
data(){
return {
hover: false,
}
}
}
</script>
How do I fix this?
<li v-for="{link, index} in navLinks" :key="index"...
should be
<li v-for="(link, index) in navLinks" :key="index"...
As it's now (destructured), link refers to a link property inside the object, not the object itself. Additionally, index is probably undefined, since the navLinks objects probably don't have an explicit property index. Therefore Vue might also complain about using invalid indexes in v-for.
Since you're only using the path prop, you could actually use destructuring, like this:
<li v-for="({ path }, index) in navLinks" :key="index"
#mouseover="hover = true"
#mouseleave="hover = false">
<router-link :to="path">
</li>
Another, unrelated note: hover property is currently being shared across all navLinks. If you expect it to somehow be related to the currently hovered element, yo uhave to save that separately (probably inside the navLink itself).
As for :nav-links="navLinks", what you've done is not only perfectly legal, but the recommended way of doing it (it's according to the HTML spec). Using :navLinks="navLinks" relies on Vue's HTML parser, which converts it to nav-links behind the scenes - inspect the HTML element and you'll notice it).
If you want to get into the details, you could have a look at this discussion on the subject. The result was: use either, but if you use camelCase it will be inconsistent with the rendered markup. If you use kebab-case, it will be consistent with rendered markup, so you won't have to deal with this difference when writing tests, for example, should you ever need to select elements by their attributes (jest converts camelCase to lowercase - hence it's inconsistent with the rendered markup, so the tests start passing/failing based on whether mount or shallowMount is used. Goes without saying, that's not really a good testing setup. )
The same exact discussion goes for using <SomeComponent /> vs <some-component />. While both work, using first needs to be addressed when writing tests if you need to select stubbed subcomponents.
Besides, vue/attribute-hyphenation (the way you did it) is part of the following vue linting presets:
plugin:vue/strongly-recommended
plugin:vue/vue3-recommended
plugin:vue/recommended
A prop in the Navbar component is named navLinks but you access it outside as nav-links.
This should work:
:navLinks="navLinks"
Incorrect syntax for v-for with {}. Use ():
li v-for="(link, index) in navLinks
You have done two mistakes here.
one is:
<template>
<div id="app">
<Navbar
:nav-links="navLinks"
/>
<router-view/>
</div>
Here you are binding with different name(nav-links), you should keep same name with which you are binding data and the name inside the props(navLinks).
Both names should be same.
Second one:
v-for="{link, index} in navLinks"
The syntax is wrong, the correct syntax should be
v-for="(link, index) in navLinks"
I'm creating a to-do list in vue.js and the following piece of code in the child element returns undefined
<input v-model="titleText" type='text'>
<input v-model="projectText" type='text'>
<button class='ui basic blue button' v-on:click="sendForm()">
Create
</button>
<button class='ui basic red button' v-on:click="closeForm()">
Cancel
</button>
sendForm () {
if (this.titleText.length > 0 && this.projectText.length > 0) {
this.$emit('create-todo', {
title: this.titleText,
project: this.projectText,
done: false
})
}
this.titleText = ''
this.projectText = ''
this.isCreating = false
}
}
Parent element:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo()"></create-todo>
</div>
</template>
methods: {
createTodo (newTodo) {
console.log(newTodo)
this.todos.push(newTodo)
}
}
The problem is a simple one: when you handle the create-todo event, you're explicitly invoking the createTodo() method without allowing any arguments to be passed to it. You must either allow Vue to interpret which arguments to supply implicitly by omitting the parentheses, or use a combination of spread syntax and the arguments object to explicitly pass all arguments provided by the emitted event into the method call.
Using omission:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo"></create-todo>
</div>
</template>
Using spread syntax + arguments object:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo(...arguments)"></create-todo>
</div>
</template>
Either solution is acceptable. If, however, you need to be able to include additional arguments into your method call in the future, then using the spread syntax + arguments object would be necessary with the additional arguments supplied before the event arguments. For example, if you wanted to supply the values "foo" and "bar" to createTodo() as well, then you would do the following:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo('foo', 'bar', ...arguments)"></create-todo>
</div>
</template>
You haven't shown the v-dom of your parent component, but perhaps you didn't insert your #create-todo listener into the custom component tag. I don't have 50 rep, so I can't comment and ask.
I have prepared tag input control in Vue with tag grouping. Templates includes:
<script type="text/x-template" id="tem_vtags">
<div class="v-tags">
<ul>
<li v-for="(item, index) in model.items" :key="index" :data-group="getGroupName(item)"><div :data-value="item"><span v-if="typeof model.tagRenderer != 'function'">{{ item }}</span><span v-if="typeof model.tagRenderer == 'function'" v-html="tagRender(item)"></span></div><div data-remove="" #click="remove(index)">x</div></li>
</ul>
<textarea v-model="input" placeholder="type value and hit enter" #keydown="inputKeydown($event,input)"></textarea>
<button v-on:click="add(input)">Apply</button>
</div>
</script>
I have defined component method called .getGroupName() which relays on other function called .qualifier() that can be set over props.
My problem: once I add any tags to collection (.items) when i type anything into textarea for each keydown .getGroupName() seems to be called. It looks like entering anything to textarea results all component rerender?
Do you know how to avoid this behavior? I expect .getGroupName to be called only when new tag is added.
Heres the full code:
https://codepen.io/anon/pen/bKOJjo?editors=1011 (i have placed debugger; to catch when runtime enters .qualifier().
Any help appriciated.
It Man
TL;DR;
You can't, what you can do is optimize to reduce function calls.
the redraws are dynamic, triggered by data change. because you have functions (v-model and #keydown) you will update the data. The issue is that when you call a function: :data-group="getGroupName(item)" it will execute every time, because it makes no assumptions on what data may have changed.
One way of dealing with is is setting groupName as a computed key-val object that you can look up without calling the function. Then you can use :data-group="getGroupName[item]" without calling the function on redraw. The same should be done for v-html="tagRender(item)"
Instead of trying to fight how the framework handles data input events and rendering, instead use it to your advantage:
new Vue({
el: '#app',
template: '#example',
data() {
return {
someInput: '',
someInputStore: []
};
},
methods: {
add() {
if (this.someInputStore.indexOf(this.someInput) === -1) {
this.someInputStore.push(this.someInput);
this.someInput = '';
}
},
}
});
<html>
<body>
<div id="app"></div>
<template id="example">
<div>
<textarea v-model="someInput" #keyup.enter.exact="add" #keyup.shift.enter=""></textarea>
<button #click="add">click to add new input</button>
<div>
{{ someInputStore }}
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
</body>
</html>
Event modifiers modifying
In the example, you can see that I am using 4 different event modifiers in order to achieve the desired outcome, but I will focus on the combination of them here:
#keyup.enter.exact - allows control of the exact combination of system modifiers needed to trigger an event. In this case, we are looking for the enter button.
#keyup.shift.enter - this is the interesting bit. Instead of trying to hackily prevent the framework from firing on both events, we can use this (and a blank value) to prevent the event we added into #keyup.enter.exact from firing. I must note that ="" is critical to the whole setup working properly. Without it, you aren't giving vue an alternative to firing the add method, as shown by my example.
I hope this helps!