I'm looping over some messages in a chat app and adding date division element that shows the date of sending of the messages under it, but when I have multiple messages sent on the same day, the divider doesn't work as expected and goes between messages that should be in the same date group. I have a function that gets the date and formats it, then returns an object with the formatted date(dd-mm-yyyy) and time(hour-minute AM/PM). When I add a function inside the {#each} loop to console.log the result of the formatDate(), it shows in the console in reversed order. For example, if I have a message sent at 8:35 AM, and a second one sent at 8:37 AM, in the console it shows the 8:37 first, then the 8:35. The array that contains the messages is sorted in chronological order, and when I display the indexes of elements in the array or log them to the console, they are in order. I think there might be something about some asynchronous stuff going on that makes the loop reference values in a different order.
let datesGroup: string[] = [];
// Function to get the moment of the day and of the year when the message was sent
function formatDate(dateStr: string, addToArray: boolean = false): MessageDate {
if (!dateStr) return { ofYear: '', ofDay: '' };
// Set pointers in time
const date = new Date(dateStr);
const todayDate = new Date();
const { hour, minute, meridian, day, month, year } = getDateValues(date);
const yearDate = `${day}-${month}-${year}`;
const time = `${hour}:${minute} ${meridian}`;
const today = `${getDateValues(todayDate).day}-${getDateValues(todayDate).month}-${getDateValues(todayDate).year}`;
let isNewDate = false;
// Check if a message is part of a different date group
if (datesGroup.indexOf(yearDate) === -1) isNewDate = true;
// Add the date divider to the array
if (addToArray && isNewDate) datesGroup = [...datesGroup, yearDate];
return {
isNewDate: isNewDate,
ofDay: time,
ofYear: today === yearDate ? 'Today' : yearDate
};
}
function getDateValues(date: Date) {
return {
minute: date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes(),
hour: date.getHours() > 12 ? date.getHours() - 12 : date.getHours(),
day: date.getDate() > 9 ? date.getDate() : '0' + date.getDate(),
month: date.getMonth() > 9 ? date.getMonth() : '0' + date.getMonth(),
year: date.getFullYear(),
meridian: date.getHours() > 12 ? 'PM' : 'AM'
};
}
{#each messages as message (message.id)}
{#if formatDate(String(message.sentat), false).isNewDate}
<div class="date-display">
{formatDate(String(message.sentat), true).ofYear}
</div>
{/if}
<div>
{#if message.from != $page.data.USER.id}
<img
class="msg-profile-picture"
alt="pfp"
src={getProfilePicture(message.from, currentRoomMembers)}
/>
{/if}
<div class="msg-content" class:sent-by-me={$page.data.USER.id === message.from}>
{#if message.from !== $page.data.USER.id}
<span
><a href="/profiles/{message.from}">{GetUsername(message.from, currentRoomMembers)}</a
></span
>
{/if}
<div class:shortened={message.text.length > 1400 && !message.shortened}>
{message.text.length < 1400
? message.text
: message.text.split('').slice(0, 1400).join('')}
{#if message.text.length > 1400}
<button type="button" class="show-more">Show more</button>
{/if}
</div>
<span>{formatDate(String(message.sentat), false).ofDay}</span>
</div>
</div>
{/each}
I tried referencing individual messages by their index, but it still was reversed
The issue you're facing is likely due to the asynchronous nature of the rendering process in Svelte. When you execute a function inside the {#each} loop, the function is called for each iteration of the loop, but the results may not be returned in the same order as the loop iteration.
To address this, you can try the following approach:
// Precompute the date groups
let datesGroup: string[] = [];
messages.forEach((message) => {
const { ofYear } = formatDate(String(message.sentat), true);
if (!datesGroup.includes(ofYear)) {
datesGroup = [...datesGroup, ofYear];
}
});
{#each messages as message (message.id)}
{#if datesGroup.indexOf(formatDate(String(message.sentat), false).ofYear) !== -1}
<div class="date-display">
{formatDate(String(message.sentat), false).ofYear}
</div>
{/if}
{/* Rest of the loop content */}
{/each}
// Memoized formatDate function
const memoizedFormatDate = memoize((dateStr) => formatDate(dateStr, false));
{#each messages as message (message.id)}
{#if memoizedFormatDate(String(message.sentat)).isNewDate}
<div class="date-display">
{memoizedFormatDate(String(message.sentat)).ofYear}
</div>
{/if}
{/* Rest of the loop content */}
{/each}
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
} else {
const result = fn.apply(this, args);
cache.set(key, result);
return result;
}
};
}