Vue JS Sort Date with components using Computed Property - vue.js

I tried to sort the date using computed properties but it didnt work, if i using at methods and remove the slice, then it will sort as expected. I've tried to split the date also but still not working. I am not sure what caused the problem.
Kindly find my code as below.
App
<template>
<div>
<h2>Ingredients</h2>
<ul>
<li></li>
</ul>
<ingredients-list v-for="(ingredient,index) in ingredients"
:key="index"
:index='index'
:foodName="ingredient.food"
:foodExpiry="ingredient.expiryDate">
</ingredients-list>
</div>
</template>
<script>
export default {
data(){
return{
ingredients:[
{
food:'carrot',
expiryDate:'2020-12-12'
},
{
food:'papaya',
expiryDate:'2018-1-15'
},
{
food:'orange',
expiryDate:'2021-10-13'
},
{
food:'meat',
expiryDate:'2019-4-23'
}]
}
},
computed: {
sortedItems() {
return this.ingredients.slice().sort( ( a, b) => {
return new Date(a.expiryDate)- new Date(b.expiryDate);
});
}
}
}
</script>
components
<template>
<div>
<h2>{{index}}</h2>
<h2>Food:{{foodName}}</h2>
<h2>Expiry:{{foodExpiry}}</h2>
</div>
</template>
<script>
export default {
props:['foodName','foodExpiry'],
}
</script>

Like #Anatoly said
Computed props are never calculated if they are not used at all.
What you should be doing to solve the problem is :
Using slice method instead of splice since it mutates the original array:
sortedItems() {
return this.ingredients.slice()
.sort((a, b) => new Date(a.expiryDate)- new Date(b.expiryDate));
}
Loop through the computed property and not the original array:
<ingredients-list v-for="(ingredient,index) in sortedItems"
:key="index"
:index='index'
:foodName="ingredient.food"
:foodExpiry="ingredient.expiryDate">
</ingredients-list>

It seems you confused splice with slice. You need slice to get a copy of an array and to sort this copy:
sortedItems() {
return this.ingredients.slice().sort( ( a, b) => {
return new Date(a.expiryDate)- new Date(b.expiryDate);
});
}

Related

Vue: Updating a data property triggers re-evaluation of entire data?

I've been working on Vue project for almost a year, and I've just observed unexpected behavior below for the first time today...
Here is a link to code sandbox:
https://codesandbox.io/s/2bnem?file=/src/App.vue
And a code snippet from above link:
<template>
<div>
<div>{{a}}</div>
<div>{{translator(b)}}</div>
<input v-model="a" />
</div>
</template>
<script>
export default {
data() {
return {
a: 'a',
b: 'b',
}
},
computed: {
translator: function() {
return function(value) {
console.log(`translated...: ${value}`)
return value
}
}
}
}
</script>
Now every time I hit the key on input, the translator is triggered.
Is this a correct behavior?
If so, what is the cause of this problem or a background reasoning of this behavior?
Note that my vue version is 2.6.14(latest).
Your original issue is that you were attempting to use a method to render parts of your template. Methods used like this will execute for every update cycle, regardless of what changed.
The better solution is to use a computed property. Here's a somewhat dynamic example that wraps each of your data properties with a computed translator_x property
<template>
<div>
<div>{{ a }}</div>
<div>{{ translator_b }}</div>
<input v-model="a" />
</div>
</template>
<script>
const defaultData = {
a: "a",
b: "b"
}
export default {
data: () => ({ ...defaultData }),
computed: Object.fromEntries(Object.keys(defaultData).map(k => [
`translator_${k}`,
vm => {
console.log("translated...", k)
return vm[k]
}
]))
};
</script>
Each translator_x property will only be evaluated if the underlying data property is changed.

Vue stored valued through props not being reactive

So I pass value using [props] and stored it in child component's data. However, when passing [props] value changes from parent, it's not updating in child component's data. Is there a fix for this..?
Here is the link to w3 test (I tried to clarify the problem as much as possible here)
<div id='app'>
<div id='parent'>
<button #click='current_value()'>Click to see parent value</button>
<br><br>
<button #click='change_value($event)'>{{ txt }}</button>
<br><br>
<child-comp :test-prop='passing_data'></child-comp>
</div>
<br><br>
<center><code>As you can see, this methods is <b>NOT</b> reactive!</code></center>
</div>
<script>
new Vue({
el: "#parent",
data: {
passing_data: 'Value',
txt: 'Click to change value'
},
methods: {
current_value(){
alert(this.passing_data);
},
change_value(e){
this.passing_data = 'New Vaule!!';
this.txt = 'Now click above button again to see new value';
e.target.style.backgroundColor = 'red';
e.target.style.color = 'white';
}
},
components: {
"child-comp": {
template: `
<button #click='test()'>Click here to see child (stored) value</button>
`,
props: ['test-prop'],
data(){
return {
stored_data: this.testProp
}
},
methods: {
test(){
alert(this.stored_data);
}
},
watch: {
stored_data(){
this.stored_data = this.testProp;
}
}
}
}
});
Props have one way data flow, that's why it doesn't react when you update it from the parent component. Define a clone of your prop at data to make it reactive, and then you can change the value within the child component.
Short answer: you don't need stored_data. Use alert(this.testProp) directly.
Long answer: when child component is created, stored_data get it's value from this.testProp. But data is local, it won't change automatically. That's why you need to watch testProp and set it again. But is not working because of a simple mistake, your watch should be:
watch: {
testProp(){ // here was the mistake
this.stored_data = this.testProp;
}
}

Pass Vue js search filter functionality through single file components with EventBus

I have the following components:
/components/SearchBlogs.vue Search component to filter on blog.title and blog.description.
/components/BlogList.vue Here I list all the Blog items.
SearchBlogs.vue
<template>
<div>
<input type="text" v-model="search" #change="emitSearchValue" placeholder="search blog">
</div>
</template>
<script>
import { EventBus } from '../event-bus.js'
export default {
name: 'SearchBlogs',
data: () => {
return {
search: ''
}
},
methods: {
emitSearchValue() {
EventBus.$emit('search-value', 'this.search')
}
}
}
</script>
BlogList.vue
<template>
<div>
<div v-for="blog in filteredBlogs" :key="blog">
<BlogListItem :blog="blog" />
</div>
</div>
</template>
<script>
import BlogListItem from './BlogListItem'
import { EventBus } from '../event-bus.js'
export default {
name: 'BlogList',
components: {
BlogListItem,
},
data: () => {
return {
blogs: [],
searchvalue: ''
}
},
computed: {
filteredBlogs() {
return this.blogs.filter(blog =>
blog.name.toLowerCase().includes(
this.searchvalue.toLowerCase()
)
)
}
},
created() {
fetch('http://localhost:3000/blogs')
.then(response => {
return response.json();
})
.then(data => {
this.blogs = data;
}),
EventBus.$on('search-value', (search) => {
this.searchvalue = value;
})
}
}
</script>
In another page component Blogs I register both components:
<template>
<div>
<h1>Blog</h1>
<TheSidebar>
<SearchBlogs />
</TheSidebar>
<BlogList/>
</div>
</template>
Can anybody see what's missing here? I want, as soon as the user types something in the search input (from the SearchBlogs.vue component), it start filtering and updating the list.
Look at my solution condesandbox
Here is an explanation:
You don't need to use EventBus. You can communicate with Search Component by v-model, using prop value and emiting updated value from the Input.
Then your Main (List) Component is responsible for all the logic.
It keeps the state of a Search
It keeps the items and filtered Items
Thanks to that your Search Component is very clear and has no data, that means it has very little responsibility.
Please ask questions if I can add something to help you understand 😉
UPDATE:
EventBus is a great addition in some cases. Your case is simple enough, there is no need to add it. Right now your architecture is "over engineered".
When you have added listener on EventBus, on created:hookyou should always remove it while Component is being destroyed. Otherwise you can encounter a trouble with double calling function etc. This is very hard to debug, tryst me I'he been there 😉
Going with my suggestion gives you comfort of "no-need-to-remember-about-this" because Vue is doing it for you.
Hope that help.
Couple of issues but essentially the computed prop filteredData will look like:
computed: {
filteredData() {
return this.experiences.filter(
el => el.category.indexOf(this.search) > -1
);
}
}
Also, used quotes around 'this.search' when passing its value back which made it a string.
Fixed sandbox
https://codesandbox.io/s/reverent-lamarr-is8jz

Vue: Using input value in function

I am using Single File Components and I have a modal component that has an
input box but I can't get the value of the input in a function below using the v-modal name. It keeps coming back as 'name is not defined'. Am I using the v-model attribute incorrectly?
<template>
<input v-model="name" class="name"></input>
</template>
<script>
export default {
methods: {
applyName() {
let nameData = {{name}}
}
}
}
</script>
You're right, you're using the v-model property incorrectly.
First off you need to define a piece of state in your component, using data:
export default {
data: () => ({
name: '',
}),
methods: {
log() {
console.log(this.name);
}
}
}
You can then bind this piece of data in your component using v-model="name", just like you did. However, if you want to access this piece of state in your method, you should be using this.name in your applyName() method.
Your {{name}} syntax is used to get access to the data in your template, like so:
<template>
<span>
My name is: {{name}}!
</span>
</template>
You have to use this pointer to access the model:
<template>
<input v-model="inputName" class="name"></input>
</template>
<script>
export default {
data() {
return {
inputName: '',
}
},
methods: {
applyName() {
// Notice the use of this pointer
let nameData = { name: this.inputName };
}
}
}
</script>
Look at the doc https://v2.vuejs.org/v2/guide/forms.html#v-model-with-Components
In the template, you are referring by name to data, computed or methods. In this case, it refers to data. When the input changes the name then the data is updated.
It is possible to use in a function referring to this.
<template>
<input v-model="name" class="name"></input>
</template>
<script>
export default {
data() {
return { name: '' }
},
methods: {
applyName() {
let nameData = this.name
}
}
}
</script>

can not assign props to data

This is a simple component. I'm trying to assign props to data as docs said so. (the initialData comes from vuex and database)
<template>
<section>
{{ initialData }}
{{ privateData }}
</section>
</template>
<script>
export default {
name: 'someName',
props: [
'initialData'
],
data() {
return {
privateData: this.initialData
};
}
};
But, the problem is initialData is OK, but privateData is just an empty object {}.
Weirdest thing is, if I save my file again, so webpack hot reloads stuff, privateData also gets the proper data I need.
Here is the parent:
<template>
<section v-if="initialData">
<child :initial-data="initialData"></micro-movies>
</section>
</template>
<script>
export default {
name: 'parentName',
data() {
return {};
},
computed: {
initialData() {
return this.$store.state.initialData;
}
},
components: {
child
}
};
</script>
I know that it's about getting data dynamically . because if I change initialData in parent to some object manually, it works fine.
The data function is only ever called once at component creation. If initialData is not populated at that point in time, then privateData will always be null. That is why you probably want to use a computed property, or watch the property.