vue3 render template syntax (component) in loop - javascript

I have the following vue3 code:
<template>
{{click}}
<ol>
<li v-for="item in items" :key="item" v-html="item"></li>
</ol>
</template>
<script setup>
const click = ref();
const items = [
'<p>text</p><a #click="click+1">click</a>',
'<p>text2</p><p><router-link :to="{name: \'home\'}">home</router-link></p>'
];
</script>
How can I get the #click and router-link component to be compiled/rendered correctly in the output?
Vue does not render the string content to html. Does anyone have an idea how I can get this to work? I don't wont to hard code the li-items.

Your code is a big red flag. Thats not the way how to work with vue.js
Here an example how it could be done:
<template>
<ol>
<li v-for="item in items" :key="item.link" >
<router-link :to="item.link">{{ item.name }}</router-link>
</li>
</ol>
</template>
<script setup>
const click = ref();
const items = ref([
{
name: "Link1",
link: "/home"
},
{
name: "Link2",
link: "/login"
}
]);
</script>
I have no idea what this code is supposed to do #click="click+1"
Also, the v-html attribute isnt a attribute that is common used. Because its also dangerous and leads to XSS vulnerabilities if you are not carefully.

Related

Accessing children as a prop in a Vue component

This may not be the Vue way to do things, but in React if I wanted to make a FancyList component I could do something like this
const FancyList : React.SFC<{},{}> ({children}) => (
<ul>
{...children}
</ul>
);
const FancyListItem : React.SFC<{text: string}, {}> ({children}) => <li>{...children}</li>
const App = React.SFC<{},{}> () => (
<FancyList>
<FancyListItem>foo</FancyListItem>
<FancyList>
);
This would render a <ul> with <li> children. I am trying to do something similar in Vue, but passing children around doesn't seem to be the thing to do, at least not from what I can see in the documentation. Here is what I have tried in the parent component. I have registered my FancyListItem component to the parent, and to the root. In the parent component the template looks like this
<ul>
<li v-for child in $root.$children>
</ul>
or this
<ul>
<li v-for child in children>
</ul>
In the root the template looks like this
<FancyList>
<FancyListItem>Some Text</FancyListItem>
<FancyListItem>Some Other Text</FancyListItem>
</FancyList>
In either case I can't get anything to render to the ul.
You are right, that's not the Vue way.
In vue you would use slots:
This allows you to compose components like this:
<navigation-link url="/profile">
Your Profile
</navigation-link>
Then in the template for <navigation-link>, you might have:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
https://v2.vuejs.org/v2/guide/components-slots.html
The Vue way of doing this is:
fancyListItem.vue:
<template>
<li>
<slot></slot>
</li>
</template>
fancyList.vue:
<template>
<ul>
<slot></slot>
</ul>
</template>
Root:
<template>
<fancy-list>
<fancy-list-item v-for="item in items" :key="item.id">
{{ item.text }}
</fancy-list-item>
</fancy-list>
</template>
<script>
export default {
components: {
FancyList: ()=>import('fancyList.vue'),
FancyListItem: ()=>import('fancyListItem.vue')
},
data(){
return {
items: [
//...items
]
}
}
}
</script>

Vue JS - Getters return the object instead the actual value in the store

I am unable to get the customer 'dad' in my store using getters. But, it shows on the screen the objects like this [ "dad" ]. How am I supposed to correct this?. I am a newbie to vue.js
<template>
<div class="hello">
<p>{{ GET_CUSTOMERS }}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed:{
...mapGetters([
'GET_CUSTOMERS'
])
}
</script>
My store
state: {
customers: ['dad']
],
getters: {
GET_CUSTOMERS(state){
return state.customers;
}
}
Your customers data is an array. When you render an array directly in a template like {{ ['dad'] }}, Vue will render it in the DOM as [ "dad" ].
I take it you want to render just one customer? You can render the first customer like this:
{{ GET_CUSTOMERS[0] }}
To render each customer, you will need to loop over each customer using v-for:
<ul>
<li v-for="(customer, i) of GET_CUSTOMERS" :key="i">
{{ customer }}
</li>
</ul>
Or, if you prefer, you can just join each customer into a single string with each customer separated by a comma:
{{ GET_CUSTOMERS.join(', ') }}

v-model on input change is heavy on performance

So I have a page rendering a v-list based on an array like so :
<v-list-tile v-for="item in array">
{{item}}
</v-list-tile>
and a dialog with a v-text-field :
<v-dialog>
<v-text-field v-model="myInput">
</v-text-field>
</v-dialog>
For now it's pretty normal.
But with a performance test, I saw that for every event triggered by a change on myInput model (like a key press) the v-for is also triggered re-rendering the list when they are actually not related.
On my huge array, it's a serious problem and make the UI really laggy. I think it's a normal behavior for a vuejs application, but I was wondering if I could precisely tell wish element to check for re-rendering.
I tried some v-if statements but it didn't do the trick.
I hope that there is an answer to that, i guess i'm missing something.
If you want to test what i'm talking about here is a ready to go html file, please debug it with your debug console, you will see a [vue warn] message of the duplicated key attesting of the fact that the v-for is indeed called for every key press.
Imagine now if the array (here items) is way bigger than that, and wrapped into complex components, making that call is just too heavy on performance when we are just aiming to change the "myInput" value.
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{data}}
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
<input v-model="data"></input>
</div>
</body>
<script>
new Vue({
el: '#app',
data: () => ({
data: '',
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
</script>
</html>
Here's a codepen showing the inner loop in its own component
Codepen.io
I've added Date.now() after items[x].message list items to show when the list is being rerendered.
In case codepen ever goes down:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
Main vue: {{data}}
<loop-component :data="loopdata"></loop-component>
<input v-model="data"></input>
<input v-model="loopdata"></input>
</div>
<script>
Vue.component('loop-component', {
props: ['data'],
data() {
return {
items: [
{message: 'Foo'},
{message: 'Bar'}
]
}
},
template: `
<div>
Loop component: {{ data }}
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.message + ' Date.now(): ' + Date.now() }}
</li>
</ul>
</div>
`
});
let app = new Vue({
el: '#app',
data: () => ({
data: '',
'loopdata': '',
items: [
{message: 'Foo'},
{message: 'Bar'},
]
}),
});
</script>
</body>
</html>
Try using .lazy modifier to sync after change events.
<input v-model.lazy="data"></input>
https://v2.vuejs.org/v2/guide/forms.html#lazy
EDIT
#IVO GELOV is right, when a component changes, this re-render. The solution is split your component into several child components.
https://v2.vuejs.org/v2/guide/reactivity.html
This is a code using slots to make it look like your example.
HTML
<div id="app">
<new-component>
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
</new-component>
</div>
Javascript
Vue.component('new-component', {
data: () => {
return {
data: ''
}
},
template: `
<div>
<div>{{ data }}</div>
<slot></slot>
<input v-model="data"></input>
</div>`
})
new Vue({
el: '#app',
data: () => ({
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
Since Vue 2.0+ whenever a change is detected - the whole component is re-rendered. If you want to avoid that - split your component into several child components.
Your example does not prove your point - the fact that there is a warning about duplicate keys inside the v-for does not mean that v-for is re-evaluated on each keypress. To confirm my statement - just change your code like this:
<li v-for="(item,idx) in items" :key="idx">
Now there is no warning.

Vue2.js and Firebase looping with v-for and :key can't get value

Looping through a Firebase project using v-for and having a devil of a time trying to get one value (imgurl) for each item. Here are a couple rows form the firebase object:
firebase data
Here is my script code in App.vue.
<script>
import Firebase from "firebase";
let config = {
…all correct blah blah…
};
let app = Firebase.initializeApp(config);
let db = app.database();
let itemsRef = db.ref("tblItems");
export default {
name: "app",
firebase: {
items: itemsRef
},
data() {
return {
styleObject: {
backgroundImage: "" //RIGHT IN HERE I NEED item.imgname BUT CAN'T FIGURE OUT SYNTAX.
}
};
}
};
Here is where I loop through using v-for and key and I call StyleObject.
<template>
<div id="app">
<div v-for="item in items" :key="item.id" v-bind:style="styleObject">
<h1>{{ item.title }}</h1>
<h2>{{ item.author }}</h2>
</div>
<router-view/>
</div>
</template>
Everything works fine, except I can't figure you how to get item.imgname where the RIGHT IN HERE comment is so I can use it for a background image in a style= attribute (each div has its own background image). Any help much appreciated.
In my opinion the easiest way to do that is to make de div into a new component and to pass the image name as a prop into it. Your component will then look like this:
Vue.component('exampleDiv', {
props: ['imgname','title','author'],
template: '<div :style="styleObject">
<h1>{{ title }}</h1>
<h2>{{ author }}</h2>
</div> ',
data() {
return {
styleObject: {
backgroundImage: this.imgname
}
}
})
You can then call the component in your code like this:
<template>
<div id="app">
<exampleDiv v-for="item in items"
:key="item.id" :title="item.title"
:author="item.author" :imgname = "item.imgname">
</exampleDiv>
<router-view/>
</div>
</template>

How to set a component non-reactive data in Vue 2?

I have a categories array, which is loaded once (in created hook) and then it is static all the time. I render these array values in a component template.
<template>
<ul>
<li v-for="item in myArray">{{ item }}</li>
</ul>
</template>
My data property looks (it does not include myArray - I don't want reactive binding):
data() {
return {
someReactiveData: [1, 2, 3]
};
}
My create hook:
created() {
// ...
this.myArray = ["value 1", "value 2"];
// ...
}
Problem is, that Vue throws error - I can't use myArray in a template, because this variable is not created when the DOM is created - mounted.
So how to do this? Or where can be stored component constants?
Vue sets all the properties in the data option to setters/getters to make them reactive. See Reactivity in depth
Since you want myArray to be static you can create it as a custom option which can be accessed using vm.$options
export default{
data() {
return{
someReactiveData: [1, 2, 3]
}
},
//custom option name myArray
myArray: null,
created() {
//access the custom option using $options
this.$options.myArray = ["value 1", "value 2"];
}
}
you can iterate over this custom options in your template as follows:
<template>
<ul>
<li v-for="item in $options.myArray">{{ item }}</li>
</ul>
</template>
Here is the fiddle
Actually, setting properties on this in created() should work out of the box:
<template>
<div id="app">
<ul>
<li v-for="item in myArray" :key="item">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "app",
created() {
this.myArray = [
'item 1',
'item 2'
];
}
};
</script>
will render
<div id="app">
<ul>
<li>
item 1
</li>
<li>
item 2
</li>
</ul>
</div>
Demo here: https://codesandbox.io/s/r0yqj2orpn .
I prefer using static data (non reactive) like this:
Create a mixin (i name it static_data.js) with the follow content
import Vue from 'vue'
Vue.prototype.$static = {}
export default {
beforeCreate () {
const vue_static = this.$options.static
const vue_static_destination = this.$static || this
if (vue_static && typeof(vue_static) === 'function') {
Object.assign(vue_static_destination, vue_static.apply(this))
} else if (vue_static && typeof(vue_static) === 'object') {
Object.assign(vue_static_destination, vue_static)
}
}
}
In your components where you want to use static data you can do:
import use_static_data from '#mixins/static_data'
export default {
mixins: [use_static_data],
static: () => ({
static_value: 'Vue is awesome'
}),
created () {
console.log(this.$static.static_value); // Vue is awesome
}
}
There is also a package vue-static
Credits here.
If you want to keep it in data, the proper way is using Object.freeze(), as described in the documentation:
The only exception to this being the use of Object.freeze(), which
prevents existing properties from being changed, which also means the
reactivity system can’t track changes.
You can try this line of code. You can copy an object and remove the reactivity.
var newObj = JSON.parse(JSON.stringify(obj));
<template>
<div id="app">
<ul>
<li v-for="item in myArray" :key="item">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "app",
data () {
this.myArray = [
'item 1',
'item 2'
];
return {}
}
};
</script>

Categories

Resources