I'm trying to show relative time to now with momentjs, a few seconds ago, 5 minutes ago, an hour ago, etc... When the html renders, it shows the relative time, I mean, 'a few seconds' o 'an hour ago' depending on how old is the comment but it gets stuck in that state and never updates.
This is my code... Any suggestions?
const components = new Set();
// Force update every component at the same time periodically
setInterval(() => {
for (const comp of components) {
comp.$forceUpdate();
}
}, 60000);
Vue.component("FromNow", {
template: '<span :title="title">{{ text }}</span>',
props: ["date"],
created() {
components.add(this);
},
destroyed() {
components.remove(this);
},
computed: {
text() {
return moment(this.date).fromNow();
},
title() {
return moment(this.date).format("dddd, MMMM Do YYYY, h:mm:ss a");
},
},
});
var app = new Vue({
el: "#app",
data() {
return {
comments: [
{
name: "Obedi1",
id: 1,
timestamp: 1587499864177,
content: "quia et suscipit\nsuscipit recusandae",
},
{
name: "Jhonj2",
id: 2,
timestamp: 1587499872202,
content: "quia et suscipit\nsuscipit recusandae",
},
{
name: "Janek3",
id: 3,
timestamp: 1587499898749,
content: "quia et suscipit\nsuscipit recusandae",
},
{
name: "Markl4",
id: 4,
timestamp: 1587499904071,
content: "quia et suscipit\nsuscipit recusandae",
},
],
};
},
});
<div id="app">
<div v-for="(comment, index) in comments" :key="index">
<h3>{{ comment.name }}</h3>
<from-now :date="comment.timestamp"></from-now>
<p>{{ comment.content }}</p>
<br />
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="main.js"></script>
Computed cannot be used here, a computed property will never update, because return moment(this.date).fromNow(); is not a reactive dependency.
In comparison, a method invocation will always run the function whenever a re-render happens. I would then try to use a method instead of a computed property.
Related
I receive some data from an api and I want to display them on a website, divided by date.
What I want to achieve :
15 Feb Wed
Object 1 : data X, data Y
Object 2 : data X, data Y
16 Feb Thu
Object 3 : data X, data Y
Object 4 : data X, data Y
etc.
An if a new object appears in the list, it is automatically inserted to the right place?
events.data is an array of objects. Each object is an event structured like so :
_id: "63e1330539560eaef2612f84"
event_date: "2023-02-16T14:00:00.000Z"
event_link: "/testLink"
event_name: "TestName"
price: 12
What I tried :
<div v-for="date in datesList" :key="date.id">
<div>{{ date }}</div>
<div class="dateSeparator"></div>
<div v-for="event in events.data" v-bind:key="event._id">
<div v-if="event.event_date == date">
...data X, data Y...
As for the script :
var datesList = []
events.data.forEach(event => {
if (!datesList.includes(event.event_date)) {
datesList.push(event.event_date)
}
})
Problem :
An empty div is created for each event for each date.
Question :
What would be the best way to achieve the desired result ?
How can I pre-filter the events based on their date in order to get rid of the v-if ?
Solution I made :
A function that filters my data by the date needed.
const filterByDate = (date) => {
let filteredEvents = []
events.data.forEach(event => {
if (event.event_date == date) {
filteredEvents.push(event)
}
})
return filteredEvents
}
Changed the v-for :
<div v-for="event in filterByDate(date)" v-bind:key="event._id">
Your code looks fine to me. I did not see any issue in the implementation you have as per the data you shared. I just added a live demo below (In Vue 2 format just to show the behavior), You can change it to Vue 3 as per your requirement.
Live Demo :
new Vue({
el: '#app',
data: {
events: {
data: [{
_id: "63e1330539560eaef2612f84",
event_date: "2023-02-16",
event_link: "/testLink",
event_name: "TestName 1",
price: 12
}, {
_id: "63e1330539560eaef2612f85",
event_date: "2023-02-17",
event_link: "/testLink",
event_name: "TestName 2",
price: 12
}, {
_id: "63e1330539560eaef2612f86",
event_date: "2023-02-16",
event_link: "/testLink",
event_name: "TestName 3",
price: 12
}, {
_id: "63e1330539560eaef2612f87",
event_date: "2023-02-17",
event_link: "/testLink",
event_name: "TestName 4",
price: 12
}]
},
datesList: []
},
mounted() {
this.events.data.forEach(event => {
if (!this.datesList.includes(event.event_date)) {
this.datesList.push(event.event_date)
}
})
}
})
.date-container {
border: 1px solid black;
margin: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(date, index) in datesList" :key="index" class="date-container">
<div>{{ date }}</div>
<hr>
<div v-for="event in events.data" v-bind:key="event._id">
<div v-if="event.event_date === date">
{{ event.event_name }}
</div>
</div>
</div>
</div>
<template>
<div>
<h1>Hello World</h1>
<div>
<span :for="day in days">{{ day }} </span>
</div>
</div>
</template>
<script>
export default {
name: 'Hello',
data() {
return {
days: ['Mon', 'Tue', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun'],
}
},
}
</script>
I am not able to loop through days array. I am getting below error.
Error: [Vue warn]: Property or method "day" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Here is a similar question: Nuxt how to loop on an array and display the data properly with a v-for
And same as there, I do recommend generating some ids for properly handling of :key, otherwise you'll get an ESlint error.
<template>
<div>
<h1>Hello World</h1>
<div>
<span v-for="day in days" :key="day.id">
{{ day.name }}
</span>
</div>
</div>
</template>
<script>
export default {
name: 'Hello',
data() {
return {
days: [
{ id: 1, name: "Mon" },
{ id: 2, name: "Tue" },
{ id: 3, name: "Wed" },
{ id: 4, name: "Thurs" },
{ id: 5, name: "Fri" },
{ id: 6, name: "Sat" },
{ id: 7, name: "Sun" },
]
}
},
}
</script>
:key is essential, more info here: https://v2.vuejs.org/v2/style-guide/#Keyed-v-for-essential
Here a blog article explaining this: https://michaelnthiessen.com/understanding-the-key-attribute#dont-use-an-index-as-the-key
You should use v-for directive like :
<span v-for="day in days">{{ day }}</span>
the directives are prefixed by v- and they are bound by default to the component methods and properties
I have this array:
data() {
return {
landingInfo: null,
slides: [
{
title: `this`,
},
{
title: "that",
},
{
title: "those",
},
],
};
},
Which is being displayed this way:
<div
class="slide"
v-for="(slide, index) in slides"
:key="index",
}"
>
<div>
<h1 class="text-bold">{{ slide.title }}</h1>
</div>
The problem is that I'm fetching info from and api and once I try to do:
slides: [
{
title: {{ landingInfo.data.subtitle }},
},
{
title: {{ landingInfo.data.subtitle2 }},
},
{
title: {{ landingInfo.data.subtitle3 }},
},
],
Everything explodes, I am new in vue using Nuxt.js and I cannot find any solution in how to achieve that.
Can someone show me how to include the fetched info inside the array property?
PD: I already tried using "{{thefetchedinfo}}" but it takes it literally that way and displays "{{thefetchedinfo}}"
The OP doesn't provide much info on the fetch, like when it is performed or what the returned data looks like, but the common pattern goes like this...
// <template> as in the OP, then...
data() {
return {
landingInfo: null,
slides: [], // empty before the fetch
};
},
mounted () {
fetchFromTheAPI().then(landingInfo => {
// More commonly, you'd map over the returned data to form slides,
// or, trying to match the OP...
this.slides = [
{ title: landingInfo.data.subtitle },
{ title: landingInfo.data.subtitle2 }, // ... and so on
]
});
},
I'm trying to have a component representing a shopping item.
I'll have one of this component for every item in my shopping list.
I don't know how to update the parent data (the shopping list) when the child is edited (the shopping item)
Shopping List
<template>
<div id="app">
<shopping-item
v-for="(item, index) in shoppingList"
:key="index"
:propsName="item.name"
:propsQuantity="item.quantity"
#shoppingItemEdited="handleEdit"
></shopping-item>
</div>
</template>
<script>
import ShoppingItem from "./components/ShoppingItem.vue";
export default {
name: "App",
components: {
ShoppingItem,
},
data() {
return {
shoppingList: [
{ name: "apple", quantity: 8 },
{ name: "banana", quantity: 3 },
{ name: "kiwi", quantity: 7 },
{ name: "peach", quantity: 5 },
],
};
},
methods: {
handleEdit(itemEdited) {
// How to get the index of the shopping-item that has been updated ?
// shoppingList[???] = itemEdited
console.log(itemEdited);
// => {name: "white peach", quantity: "6"}
},
},
};
</script>
Shopping Item
<template>
<div>
<input v-model="name" placeholder="ex: banana" #change="updateParent" />
<input
v-model="quantity"
type="number"
placeholder="ex: 3"
#change="updateParent"
/>
</div>
</template>
<script>
export default {
data() {
return {
name: "",
quantity: null,
};
},
props: {
propsName: String,
propsQuantity: Number,
},
created() {
this.name = this.propsName;
this.quantity = this.propsQuantity;
},
methods: {
updateParent() {
this.$emit("shoppingItemEdited", {
name: this.name,
quantity: this.quantity,
});
},
},
};
</script>
So I have few questions:
How can I know witch component emited the event 'shoppingItemEdited' ? If I knew it, I could find out which shoppingList item I should update.
I red I should not update props in the child, so I create data based on props, is that a standard way of doing that ?
this.name = this.propsName;
this.quantity = this.propsQuantity;
Just pass an index to a handler: #shoppingItemEdited="handleEdit(index, $event)"
No it's not "standard" - created hook is called only once when component is created, so if value of prop changes later (from parent), data will not update. It's probably not a problem in your case but usually its better to use computed:
computed: {
name: {
get() { return this.propsName },
set(value) {
this.$emit("shoppingItemEdited", {
name: value,
quantity: this.quantity,
});
}
}
}
...handle event in parent and the change will propagate (by props) to a child
I'm a total newbie, so please bear with me if I'm still grasping with the coding fundamentals.
I want to use a template that is defined in the prop. The template is inside the DOM. The reason I want to do it this way is that I want to reuse the component logic (specifically the pagination), but may want to change how the way the template displays the data in different pages. So I wanted to seaparate the template from the script file.
This is the HTML File:
<div id="store-list">
<paginated-list :list-data="stores" use-template="#displayList"></paginated-list>
</div>
<script type="text/template" id="display-list">
<div>
<div v-for="p in paginatedData">
{{p.BusinessName}}
</div>
</div>
</script>
This is the .js file:
Vue.component('paginated-list', {
data() {
return {
pageNumber: 0
}
},
props: {
listData: {
type: Array,
required: true
},
useTemplate: {
type: String,
required: false
},
size: {
type: Number,
required: false,
default: 10
}
},
computed: {
pageCount() {
let l = this.listData.length,
s = this.size;
return Math.floor(l / s);
},
paginatedData() {
const start = this.pageNumber * this.size,
end = start + this.size;
return this.listData
.slice(start, end);
}
},
//template: document.querySelector('#display-list').innerHTML // this works
template: document.querySelector(useTemplate).innerHTML // this does not
});
var sl = new Vue({
el: '#store-list',
data: {
stores: [{
"BusinessName": "Shop Number 1"
}, {
"BusinessName": "Shop Number 2"
}, {
"BusinessName": "Shop Number 3"
}]
}
});
var shoppingList = new Vue({
el: '#shopping-list',
data: {
header: 'shopping list app',
newItem: '',
items: [
'1',
'2',
'3',
]
}
})
Any help is greatly appreciated. Thank you.
You can use the inline-template attribute to override the component's template at render time. For example
<paginated-list :list-data="stores" inline-template>
<div>
<div v-for="p in paginatedData">
{{p.BusinessName}}
</div>
</div>
</paginated-list>
See https://v2.vuejs.org/v2/guide/components-edge-cases.html#Inline-Templates
Your component can still have a default template but this will override it if set.