Do all imported arrays in Vue.js have to be reactive? - vue.js

I'm importing a simple array of 4 strings called QUIZ_DATA into my Vue.js component:
["Question1?", "Question number 2?", "This is number 3!", "And # 4"]
These 4 questions are static and won't change. I don't need them to be reactive, so I import them into my component as QUIZ_DATA, but when I try to iterate through the array, it gives me an error that the property is non-reactive.
<template>
<div v-for="(question, index) in QUIZ_DATA">
{{ question }}
</div>
</template>
<script>
import QUIZ_DATA from "~/plugins/QuizData.js";
export default {
// Nothing here yet
}
</script>
The console says:
Make sure that this property is reactive.
I got rid of the error by copying the array into my component's data object, which feels like an unnecessary step, given that the array won't change:
data(){return {quizData: QUIZ_DATA}}
Is there a way of iterating through static objects without needing to copy them into the component's data() object first?

QUIZ_DATA must be a property of the component instance if you want to access it from within the template.
Non-reactive data can be assigned to the component instance in the created hook.
created() {
this.QUIZ_DATA = QUIZ_DATA;
}

You can return your array with computed property:
const QUIZ_DATA = ["Question1?", "Question number 2?", "This is number 3!", "And # 4"]
new Vue({
el: '#demo',
computed: {
questions() {
return QUIZ_DATA
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div v-for="(question, index) in questions">
{{ question }}
</div>
</div>

Is there a way of iterating through static objects without needing to
copy them into the component's data() object first ?
Answer is Yes, ways to achieve :
By using computed property.
const QUIZ_DATA = ["Question1?", "Question number 2?", "This is number 3!", "And # 4"];
new Vue({
el: '#app',
computed: {
QUIZ_DATA() {
return QUIZ_DATA
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(question, index) in QUIZ_DATA">
{{ question }}
</div>
</div>
As data is non-reactive we can use created hook.
let QUIZ_DATA = ["Question1?", "Question number 2?", "This is number 3!", "And # 4"];
new Vue({
el: '#app',
created: function () {
this.QUIZ_DATA = QUIZ_DATA;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(question, index) in QUIZ_DATA">
{{ question }}
</div>
</div>

Related

Calling function from outside of component but rendered in v-for loop

I have some component which is rendered 6 times in v-for loop. I'm trying to do onclick function which will call method inside of specified chart component. But now i'm getting errors like this.$refs.chart1.pauseChart() is not an function. This is how i'm trying to achieve it:
<BaseChart ref="`chart$[index]`" #click="pauseChart(index)"/>
pauseChart(index) {
this.$refs.[`chart${index}`].pauseChart()
}
refs inside v-for are arrays rather than singulars. Therefore, if you have
<template v-for="(something, index) in list">
<BaseChart ref="chart" :key="index" #click="pauseChart(index)" />
</template>
you should use
methods:
{
pauseChart(idx)
{
this.$refs.chart[idx].pauseChart();
}
}
For more information - refer to Vue documentation
ref will be having an array. hence, you have to access that with 0 index.
Live Demo :
Vue.component('child', {
methods: {
pauseChart() {
console.log('child method call');
}
},
props: ['childmsg'],
template: '<p v-on="$listeners">{{ childmsg }}</p>',
});
var app = new Vue({
el: '#app',
methods: {
pauseChart(chartIndex) {
this.$refs[chartIndex][0].pauseChart();
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<div v-for="index in 6" :key="index">
<child childmsg="Hello Vue!" :ref="`chart${index}`" #click="pauseChart(`chart${index}`)">
</child>
</div>
</div>

Only show slot if it has content, when slot has no name?

As answered here, we can check if a slot has content or not. But I am using a slot which has no name:
<template>
<div id="map" v-if="!isValueNull">
<div id="map-key">{{ name }}</div>
<div id="map-value">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
name: {type: String, default: null}
},
computed: {
isValueNull() {
console.log(this.$slots)
return false;
}
}
}
</script>
I am using like this:
<my-map name="someName">{{someValue}}</my-map>
How can I not show the component when it has no value?
All slots have a name. If you don't give it a name explicitly then it'll be called default.
So you can check for $slots.default.
A word of caution though. $slots is not reactive, so when it changes it won't invalidate any computed properties that use it. However, it will trigger a re-rendering of the component, so if you use it directly in the template or via a method it should work fine.
Here's an example to illustrate that the caching of computed properties is not invalidated when the slot's contents change.
const child = {
template: `
<div>
<div>computedHasSlotContent: {{ computedHasSlotContent }}</div>
<div>methodHasSlotContent: {{ methodHasSlotContent() }}</div>
<slot></slot>
</div>
`,
computed: {
computedHasSlotContent () {
return !!this.$slots.default
}
},
methods: {
methodHasSlotContent () {
return !!this.$slots.default
}
}
}
new Vue({
components: {
child
},
el: '#app',
data () {
return {
show: true
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button #click="show = !show">Toggle</button>
<child>
<p v-if="show">Child text</p>
</child>
</div>
Why you dont pass that value as prop to map component.
<my-map :someValue="someValue" name="someName">{{someValue}}</my-map>
and in my-map add prop:
props: {
someValue:{default: null},
},
So now you just check if someValue is null:
<div id="map" v-if="!someValue">
...
</div

Show child of this parent click vue

I want to show the immediate child after clicking on its parent. Its easy on jquery, hard on vue, how can I do it?
template:
<boxes inline-template>
<div class="white-box" #click="toggleTick">Purchase only
<div v-if="showTick"><i class="fas fa-check"></i></div>
</div>
</boxes>
js
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
},
methods: {
toggleTick () {
this.showTick = !this.showTick
}
}
})
var app = new Vue({
el: '#app',
data: {
}
})
At the moment I have multiple "white-box" div, it shows the child div for all of them, I just want to show the div for the clicked parent's child.
You should have behavior that you expect :
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
}
})
var app = new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 1</div>
</div>
</boxes>
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 2</div>
</div>
</boxes>
</div>
Your white-boxes are sharing the same showTick variable, so clicking on one fo them changes the value for all of them.
There are 2 available solutions to this:
Have multiple boxes components and not multiple white-boxes under the same boxes component. Something take ends up looking like this in the DOM
<boxes>...</boxes>
<boxes>...</boxes>
<boxes>...</boxes>
Use an array for showTick and use an index when calling toggleClick. Also note that for the changes in the array to be reactive you need to use Vue.set.
I recommend the former solution.

Vue.JS Passing object to child for use inside slot

I am trying to pass an object that is loaded within DataContainer into a slot, so that the user can customise the view.
<data-container silo-id="5">
<div slot="content"> <!-- I tried :data="siloData" here but no luck -->
Your current balance is {{data.balance}}
</div>
</data-container>
So DataContainer loads the resource via http and sets the value to its 'siloData' property.
DataContainer's template has no content of its own just a placeholder for the slot.
<template>
<div>
<slot name="content"></slot>
</div>
</template>
When I try this the text is not interpolated and just remains as {{siloData.balance}} to the browser.
I have tried some examples from Vue.JS site like the todo list, but I must admit utterly confused, maybe because this is not a collection, but just a single (albeit complex) object.
Hopefully someone can point me in the right direction.
Many thanks
Phil
You can use a $emit event
Vue.component('data-container', {
template: '#data-container',
data() {
return {
siloData: {}
}
},
mounted() {
this.siloData = { name: "Silo", balance: 10 } // loading data
this.$emit('silo-loaded', this.siloData)
}
})
new Vue({
el: '#app',
data() {
return {
data: {}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<data-container class="card" #silo-loaded="val => data = val">
<div slot="content">
Your current balance is {{ data.balance }}
</div>
</data-container>
</div>
<template id="data-container">
<div>
<slot name="content"></slot>
</div>
</template>

Vue custom filtering input component

I'am trying to create a component that have 'just' an text input. String typed in this input will be used to filter a list. My problem is that I cannot handle how to share this filter string between my component and the main app that contains the list to filter.
I tried several things and most of the time I get the error :
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value
So I looked Vuex but I thinks it cannot help in this case because I can have several filter component used in he same page for different list, and I don't want them to be synchronized ^^
Here is what I have:
The filter component
<script type="x/template" id="filterTpl">
<div>
<span class="filter-wrapper">
<input type="search" class="input input-filter" v-model.trim="filter" />
</span>
</div>
</script>
<script>
Vue.component('list-filter', {
props: {
filter: String
}
template: '#filterTpl'
});
</script>
And my main app:
<div id="contacts">
<list-filter :filter="filter"></list-filter>
<ul class="contacts-list managed-list flex">
<li class="contact" v-for="contactGroup in filteredData">
[...]
</li>
</ul>
</div>
<script>
var contactsV = new Vue({
el: '#contacts',
data: {
filter: "",
studyContactsGroups: []
},
computed: {
filteredData: function(){
// Using this.filter to filter the studyContactsGroups data
[...]
return filteredContacts;
}
}
});
</script>
Thanks for any help or tips :)
You can synchronize child value and parent prop either via explicit prop-event connection or more concise v-bind with sync modifier:
new Vue({
el: '#app',
data: {
rawData: ['John', 'Jane', 'Jim', 'Eddy', 'Maggy', 'Trump', 'Che'],
filter: ''
},
components: {
'my-input' : {
// bind prop 'query' to value and
// #input update parent prop 'filter' via event used with '.sync'
template: `<input :value="query" #input="updateFilter">`,
props: ['query'],
methods: {
updateFilter: function(e) {
this.$emit('update:query', e.target.value) // this is described in documentation
}
}
}
},
computed: {
filteredData: function() {
// simple filter function
return this.rawData.filter(el => el.toLowerCase()
.match(this.filter.toLowerCase()))
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-input :query.sync="filter"></my-input>
<hr>
<ul>
<li v-for="line in filteredData">{{ line }}</li>
</ul>
</div>