How can I show days by group like Whatsapp chat screen?
Asked Answered
M

3

12

How can I excatly do a similar Date system like the one in the Whatsapp chat screen?

As you can see the messages are in a group by date, I mean they are separated by date.

Here is a ScreenShot that i found for better explanation:

Example

I do this in a FlatList, while rendering the messages one by one.

Here is what i did

        let previousDate = "";
    if (index > 0) {
        previousDate = moment(this.state.messages[index - 1].created_at).format(
            "L"
        );
    } else {
        previousDate = moment(this.state.messages.created_at).format("L");
    }

    let currentDate = moment(item.created_at).format("L");

So, i created a functional component for renderItem prop of the FlatList, so item and index comes from the actual data from the FlatList.

What i'm trying to do here is, basically grabbing the current rendering item's created_at and compare it with the previous item's created_at, and to do that, i'm using the original data which is stored in the state. But unfortunately when the FlatList rendering the very first item which has index number 0 there is no previous element to compare in the original data in state, that's why i checking if is greater than 0 go and grab date from previous indexed item. And in the Else case, which means when rendering the first item, do not look for previous item and just get the created_at.

And below i check if the currentDate and previousDates are NOT the same, render a custom component else do not render anything.

    {previousDate && !moment(currentDate).isSame(previousDate, "day") ? ( // custom component) : null}

It's should work like that, but the major problem is, i used inverted FlatList for to able to messages go from bottom of the screen to the top. But now, becouse of it's a inverted flatlist the items being rendering from bottom to the top and it gives me result like this:

enter image description here

NOTE: At the beginning the messages were coming also reversed but i fixed this with sending them also reversed from the DB.

So, i don't know how do i able to achieve my goal, and do it like on the first picture.

Thank you!

Mande answered 15/4, 2020 at 19:58 Comment(3)
Did you fix this ?... Trying the same but on reactSheldon
not yet, its still on my TODO listMande
@Ozzie did you manage to implement this? if you did, can you please post your answer here, I'm struggling to get this working...Allusion
B
12

I use a helper function (generateItems) to address the problem that you are describing. Here is the code that I use to group my messages by day and then render either a <Message /> or a <Day /> in the renderItem prop. This is using an inverted FlatList as you described.

import moment from 'moment';

function groupedDays(messages) {
  return messages.reduce((acc, el, i) => {
    const messageDay = moment(el.created_at).format('YYYY-MM-DD');
    if (acc[messageDay]) {
      return { ...acc, [messageDay]: acc[messageDay].concat([el]) };
    }
    return { ...acc, [messageDay]: [el] };
  }, {});
}

function generateItems(messages) {
  const days = groupedDays(messages);
  const sortedDays = Object.keys(days).sort(
    (x, y) => moment(y, 'YYYY-MM-DD').unix() - moment(x, 'YYYY-MM-DD').unix()
  );
  const items = sortedDays.reduce((acc, date) => {
    const sortedMessages = days[date].sort(
      (x, y) => new Date(y.created_at) - new Date(x.created_at)
    );
    return acc.concat([...sortedMessages, { type: 'day', date, id: date }]);
  }, []);
  return items;
}

export default generateItems;

For reference here is my list as well as the renderItem function:

<MessageList
  data={generatedItems}
  extraData={generatedItems}
  inverted
  keyExtractor={item => item.id.toString()}
  renderItem={renderItem}
/>

function renderItem({ item }) {
  if (item.type && item.type === 'day') {
    return <Day {...item} />;
  }
  return <Message {...item} />;
}
Burford answered 22/12, 2020 at 16:39 Comment(5)
First of all thank you 4 your response. Very interesting example im gonna try it out...I hope you understood my problem while writing this. becouse for me, separating the renderItwem element is it day or message, i do the similar thing inside of the item, as a title, but becouse of flatlist is inverted i was pputting the date at the end of the component instead of header. i hope your example will help me out... thank youMande
<<return messages.reduce((acc, el, i)>>, in here what are the acc, el and i.Binnacle
@SulakshithaRathnayake These are the standard variables for the reduce method: accumulator, element, and index. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Burford
Ok. got it. Thank you Matt.Binnacle
data and extraData are same function, generatedItems?Footlight
E
1

This is how i did it in react,

  1. Create a new Set() to store dates uniquely
const dates = new Set();
  1. When looping through chats array, check if date already exists in unique Set before rendering date
chats.map((chat) => {
  
  // For easier uniqueness check,
  // Formated date string example '16082021'
  const dateNum = format(chat.timestamp, 'ddMMyyyy');

  return (
    <React.Fragment key={chat.chat_key}>

      // Do not render date if it already exists in set
      {dates.has(dateNum) ? null : renderDate(chat, dateNum)}

      <ChatroomChatBubble chat={chat} />
    </React.Fragment>
  );
});
  1. Finally, when date has been rendered, add date num into array so it doesn't render again
const renderDate = (chat, dateNum) => {
  const timestampDate = format(chat.timestamp, 'EEEE, dd/MM/yyyy');

  // Add to Set so it does not render again
  dates.add(dateNum);

  return <Text>{timestampDate}</Text>;
};
Ethelyn answered 16/8, 2021 at 11:13 Comment(1)
Is this approach is using FlatList? or just .map()? If not using FlatList in RN I think is not good for the performance.Galiot
G
1

Here is the complete solution in react. Just copy it and change the array according to your requirments and it will work. For styling i am using tailwindCss. You can replace styling with your requirements.

Component MessageList

import moment from 'moment';
import React from 'react';
import Message from './message';

const MessageList = React.memo(({ messages }) => {

    const msgDates = new Set();

    const formatMsgDate = (created_date) => {

        const today = moment().startOf('day');
        const msgDate = moment(created_date);
        let dateDay = '';

        if(msgDate.isSame(today, 'day')){
            dateDay = 'Today';
        }
        else if (msgDate.isSame(today.clone().subtract(1, 'days'), 'day')){
            dateDay = 'Yesterday';
        }
        else{
            dateDay = msgDate.format('MMMM D, YYYY');
        }

        return dateDay
    }

    const renderMsgDate = (message, nextMessage) => {
        const dateTimeStamp = moment(message.created, 'YYYY-MM-DD').valueOf();
        let nextMsgdateTimeStamp = '';
        if(nextMessage){
            nextMsgdateTimeStamp = moment(nextMessage.created, 'YYYY-MM-DD').valueOf();
        }

        if(msgDates.has(dateTimeStamp) || (nextMsgdateTimeStamp && nextMsgdateTimeStamp == dateTimeStamp)){
            return null
        }
        else{
            
            msgDates.add(dateTimeStamp);

            return (
                <div className='py-2 flex justify-center items-center'>
                    <span id="msg_day" className='px-2 py-1 shadow font-medium rounded-lg text-gray-400 bg-white border-gray-300'>
                        {formatMsgDate(message.created)}
                    </span>
                </div>
            )
        }
    }

    return (
        <>
            {messages?.map((message, index) => (
                <>
                    <Message key={index} msg={message} />
                    {renderMsgDate(message, messages[index+1])}
                </>
            ))}
        </>
    );
});

export default MessageList;

Message Component

import React, { useMemo } from 'react';
import ReactTimeAgo from 'react-time-ago'
import { useGroupChatContext } from '../../context/groupchatContext';

const Message = React.memo(({ msg }) => {

  const groupChatContext = useGroupChatContext();

  const {authData} = useMemo(() => ({
    authData: groupChatContext.authData,
  }));


  return (
    <div className={`flex gap-3 pb-4 ${msg.nurse_id == authData?.id ? 'flex-row-reverse text-start' : 'w-full'}`}>


        <div className={`${msg.nurse_id == authData.id ? 'text-right' : 'text-left'}`}>
          <h2 className=" text-sm font-semibold ml-2"> {msg.nurse_id == authData.id ? null : msg.sender} <span className="text-xs text-gray-500 ml-2">{msg.status == 'sending' ? 'Sending...' : <ReactTimeAgo timeStyle="twitter-first-minute" date={new Date(msg.created)} />}</span></h2>
          <div className="mt-1 flex flex-col gap-2">
            <div className={`rounded-md ${msg.nurse_id == authData.id ? 'bg-bg-primary': 'bg-white' }  p-2 shadow`}>
              <p className={`${msg.nurse_id == authData.id ? 'text-white': 'text-gray-800' } text-start`} dangerouslySetInnerHTML={{ __html: msg.message?.text }}></p>
            </div>
          </div>
        </div>
      </div>
  );
});

export default Message;

The final Result

screenshot

Garratt answered 9/3, 2024 at 8:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.