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

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>

Related

VueJs 2: How to display prop in another component?

I have a simple question, but cannot find how to solve.
There are 2 Vue 2 Components. In Component 1, two props are passed, which therefore are used in Component 2.
// Component 1
Vue.component('component1', {
props: {
titleTest: {
type: String,
required: true
},
textTest: {
type: String,
required: true
}
},
template: `
<div>
<div :title="titleTest">{{ titleTest }}</div>
<div :data-test="textTest">{{ textTest }}</div>
</div>
`,
created() {
console.log('Created1');
this.$root.$refs.component1 = this;
},
methods: {
foo: function() {
alert('this is component1.foo');
}
}
});
// Component 2
Vue.component('component2', {
template: `
<div>
<div>Some text</div>
<ul>
<li>List Item1</li>
<li>List Item2</li>
</ul>
<div>
<button id='test' type="submit" #click="bar">Text</button>
<component1 ref="component1" :title="test1" :data-test="test2"></component1>
</div>
</div>
`,
data: function() {
return {
test1: 'testText1',
test2: 'testText2'
};
},
methods: {
bar: function() {
this.$root.$refs.component1.foo();
}
},
created: function() {
console.log('Created2');
}
});
new Vue({
el: '#plotlyExample',
});
My idea, when I use Component 1 in Component 2 HTML template and bind data variables, they should be displayed. However, Vue sets only "title" and "data-test" but {{ titleTest }}, {{ textTest }} are not displayed. Additionally Vue sets props in one div, instead of two separate.
My ideal result is:
You named your props titleTest and textTest, that means you need to pass title-test and text-test, NOT title and data-test.
The reason they end up in your main <div> is because when Vue doesn't recognise them as props (because you used different names), it falls back on assuming they're regular HTML attributes (like class, id, and style)
In order for it to work, you either need to rename your props to title and dataTest in Component1, or you should use the title-test and text-test names in Component2.
You just need pass props to the component1
<component1 ref="component1" :title-test="test1" :text-test="test2"></component1>
you are missnaming the props in the component1 ( child component ) , you used title and data-test but your props names are titleTest and textTest ! so you should use title-test and text-test instead .
:prop-name="propValue"

VueJS - calling method in v-for loop, get error 'not defined on the instance but referenced during render'

I am a Vue noobie (Vuebie?) I know this has been asked before but I haven't found a solution.
I am trying pass an object to method to += a value and display a total value.
I either get a method / prop not defined on instance but referenced during render error or variations of undefined variable errors.
This template is nested several levels deep in other templates so I'm not sure how / where to define my getCollectionsTotal method.
<template>
<div>
<ul class="c-filter__list" v-if="categories.length">
<li
class="c-filter__list__items"
v-for="(category, index) in categories"
:key="index"
#click.stop="setCurrentCategory(category)"
>
{{ getCollectionTotal(category) }} // pass the category object to my method
<a
v-bind:class="{
'is-selected': filters.category.includes(category.category_name)
}"
class="c-link c-link--filter"
>{{ category.category_name }}
<span>({{ category.collections_number }})</span>
</a>
</li>
</ul>
<div id="collectionsTotal">{{ collectionsTotal }}</div> // display the total here
</div>
</template>
<script>
import state from "../store/index";
import { mapState } from "vuex";
export default {
name: "categories-sidebar",
computed: mapState(["filters", "categories"]),
methods: {
setCurrentCategory: function(category) {
if (this.filters) {
this.filters.category.includes(category.category_name)
? state.commit("delCategory", category.category_name)
: state.commit("setCategory", category.category_name);
}
},
getCollectionTotal(cat) {
let collectionsTotal; // this says 'assigned a value but never used'
collectionsTotal += cat.collections_number;
}
}
};
</script>
You are not using mapState in a correct way.
Change this:
computed: mapState(["filters", "categories"])
into
computed: { ...mapState(["filters", "categories"]) }
For further information, you can check:
https://forum.vuejs.org/t/dont-understand-how-to-use-mapstate-from-the-docs/14454
You should to return something from your function and assign a initial value to your collectionsTotal variable:
getCollectionTotal(cat) {
let collectionsTotal = ''; // or let collectionsTotal = 0;
collectionsTotal += cat.collections_number;
return collectionsTotal;
}

Using Vuex, how do I remove items from an array when they are part of an array of objects?

Referencing the live demo code here:
https://codesandbox.io/s/vue-template-r26tg
Let's say I have a Vuex store with the following data:
const store = new Vuex.Store({
state: {
categories: [
{
name: "Category A",
items: [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }]
},
{
name: "Category B",
items: [{ name: "Item A" }, { name: "Item B" }, { name: "Item C" }]
},
{
name: "Category C",
items: [{ name: "Item !" }, { name: "Item #" }, { name: "Item #" }]
}
]
}
});
And I have an App.vue, Category.vue and Item.vue that are set up so that they are rendered like so:
//App.vue
<template>
<div id="app">
<Category v-for="(category, index) in categories" :category="category" :key="index"/>
</div>
</template>
<script>
export default {
components: { Category },
computed: {
...mapState(["categories"])
}
};
</script>
//Category.vue
<template>
<div class="category">
<div class="header">{{ category.name }}</div>
<Item v-for="(item, index) in category.items" :item="item" :key="index"/>
</div>
</template>
<script>
export default {
components: { Item },
props: {
category: { type: Object, required: true }
}
};
</script>
//Item.vue
<template>
<div class="item">
<div class="name">{{ item.name }}</div>
<div class="delete" #click="onDelete">✖</div>
</div>
</template>
<script>
export default {
props: {
item: { type: Object, required: true }
},
methods: {
onDelete() {
this.$store.commit("deleteItem", this.item);
}
}
};
</script>
In other words, App.vue gets the list of categories from Vuex, then passes it down to Category.vue as a prop for each category, then Category.vue passes down category.items to Item.vue as a prop for each item.
I need to delete an item when the delete button next to it is clicked:
However, at the Item.vue level, I only have access to the item, but not the category. If I send the item to Vuex, I have no way of telling which category it belongs to. How do I get a reference to the category so that I can delete the item from it using Vuex?
I can think of two ways:
Add a parent reference back to the category for each item. This is undesirable not only because I'd have to massage the item data, but also because it introduces a circular reference that I'd rather not have to deal with in other parts of the app.
Emit an event from Item.vue up to Category.vue and let Category.vue handle the Vuex call for deletion. This way the category and the to-be-deleted item are both known.
Is there a better way of handling this kind of deletion?
I'd strongly recommend (2). In general, if you can create a component which takes props and emits events without having other side effects (API calls, Vuex mutations, etc.) that's usually the correct path. In this case, you can probably even push the event all the way back to the parent's parent.
Where shared state (Vuex) really helps is when you have two or more components which are far away from each other in the DOM tree. E.g. imagine a header with a count of the total items. That degree of spatial separation may exist in your app, but it doesn't in this simple example.
An additional benefit to emitting an event here is that you care more easily use tools like storybook without having to deal with any Vuex workarounds.
Personally, I'd go with 2 (emit an event from Item.vue up to Category.vue), but, since you asked about possibilities, there is a third way: passing a callback function.
Example:
Category.vue:
<template>
<div class="category">
<div class="header">{{ category.name }}</div>
<Item v-for="(item, index) in category.items" :item="item" :key="index"
:on-delete="deleteItem"/>
</div>
</template>
<script>
// ...
export default {
// ...
methods: {
deleteItem(i) {
console.log('cat', this.category.name, 'item', i)
//this.$store.commit("deleteItem", this.item);
}
}
};
</script>
Item.vue:
<template>
<div class="item">
<div class="name">{{ item.name }}</div>
<div class="delete" #click="() => onDelete(this.item)">✖</div>
</div>
</template>
<script>
export default {
props: {
item: { type: Object, required: true },
onDelete: { type: Function }
},
};
</script>
Updated sandbox here. Notice, in this case, the callback is onDelete.
If this were React, the callback was for sure a more idiomatic way. In Vue, as said, I'd argue in favor of emitting the event in the child and handling it in the parent (with v-on).

Vue.js: correct way of changing a component prop from inside its method

I have the following code for a Vue.js component:
var list = Vue.component('list', {
props: ['items', 'headertext', 'placeholder'],
template: `
<div class="col s6">
<div class="search">
<searchbox v-bind:placeholder=placeholder></searchbox>
<ul class="collection with-header search-results">
<li class="collection-header"><p>{{ headertext }}</p></li>
<collection-item
v-for="item in items"
v-bind:texto="item.nome"
v-bind:link="item.link"
v-bind:key="item.id"
>
</collection-item>
</ul>
</div>
</div>`,
methods: {
atualizaBibliografiasUsandoHipotese: function (value) {
var query = gql`query {
allHipotese(nome: "${value}") {
id
nome
bibliografiasDestaque {
id
nome
link
descricao
}
}
}`;
client.query({ query }).then(function(results) {
this.items = results['data']['allHipotese'][0]['bibliografiasDestaque'];
}.bind(this));
},
}
});
And outside the component, in another javascript file, I call the atualizaBibliografiasUsandoHipotese method to make a query to the backend (GraphQL), and update the component. This works fine, however I get a warning message from Vue saying
[Vue warn]: 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. Prop being
mutated: "items"
I made several attempts to fix this warning, but none of them worked. Anyone can help me on how can I fix this warning? Thanks
As mentioned, you should not mutate props, instead you can do this:
var list = Vue.component('list', {
props: ['items', 'headertext', 'placeholder'],
template: `
<div class="col s6">
<div class="search">
<searchbox v-bind:placeholder=placeholder></searchbox>
<ul class="collection with-header search-results">
<li class="collection-header"><p>{{ headertext }}</p></li>
<collection-item
v-for="item in myItems"
v-bind:texto="item.nome"
v-bind:link="item.link"
v-bind:key="item.id"
>
</collection-item>
</ul>
</div>
</div>`,
data: ()=> { return {
myItems: this.items
}
},
methods: {
atualizaBibliografiasUsandoHipotese: function (value) {
// Your Code
this.myItems = results['data']['allHipotese'][0]['bibliografiasDestaque'];
}.bind(this));
},
});
Notice that v-for is now looping over items in myItems.
This is what you are looking for:
https://v2.vuejs.org/v2/guide/components.html#One-Way-Data-Flow
It even says:
This means you should not attempt to mutate a prop inside a child
component. If you do, Vue will warn you in the console.

Vuejs computed properties that depend on other, asynchronous, computed properties

In my Vuejs app I need to pass two computed properties to a component called avatar: an image surce and a string.
The problem is that not all items have a picture, and when they don't, vuejs throws an error because it cannot read property apples of undefined (because album_id is undefined).
The error is being thrown from that very long-winded src property in the avatar component, below:
<template>
<div class="apples">
<div id="mast" class="f3 b bb b--black-10">
<h2 class="ttc">Apple's List</h2>
</div>
<div id="content">
<li v-for="apple in apples" class="list-item">
<avatar :src="apple[ 'album_id '][ 'apples' ][ '256' ]" :size="50" :appletype="apple.type"></avatar>
</li>
</div>
</div>
</template>
<script>
import Avatar from './Avatar.vue';
import Apples from '#/api/apples';
export default {
name: 'apples',
asyncComputed: {
apples: {
async get() {
const apples = await Apples.getAll();
return apples.data;
},
default: []
}
},
components: {
Avatar
}
};
</script>
I need to somehow treat the data that I receive from the api before I use it in the html template, but I am unsure as to how to proceed. Creating a separate pictures array within the get() function just seems wrong.
Using v-if and v-else to check if there is a src property to pass down also seems very clumsy.
Do I create another, separate, computed method that iterates through my list of items and gets the picture src if the item has one?
I would suggest that you need to create getters for your apple sauce.. er.. source.. er.. src :)
<template>
<div class="apples">
<div id="mast" class="f3 b bb b--black-10">
<h2 class="ttc">Apple's List</h2>
</div>
<div id="content">
<li v-for="apple in apples" class="list-item">
<avatar :src="getAvatar(apple, 256)" :size="50" :appletype="apple.type"></avatar>
</li>
</div>
</div>
</template>
<script>
import Avatar from './Avatar.vue';
import Apples from '#/api/apples';
export default {
name: 'apples',
methods: {
getAvatar: function(obj, id) {
return obj.album_id.apples[ id ] | ''
}
},
asyncComputed: {
apples: {
async get() {
const apples = await Apples.getAll();
return apples.data;
},
default: []
}
},
components: {
Avatar
}
};
</script>
This allows you to create a graceful fallback with whatever arguments and implementation you choose.

Categories

Resources