'How does slack store unread message counts?
I'm building a chat application and I'm trying to identify the state architecture for indicating unread messages.
My state currently contains a list of messages. On render, I group them by conversation so I can render a list of recipients and, for the selected recipient, the messages.
The simplest approach would be to also store a lastRead hash of {recipient: lastReadTimestamp} in the state as well. On render, count the number of messages in each conversation whose timestamp is greater than the stored lastRead timestamp to get the number of unread messages. And when the recipient is selected, set the lastRead timestamp for that recipient to that of the most recent message.
The problem with this is if, while you're away, 15+ messages come in - to the point that some are beyond the viewport and you'd have to scroll to see them. Once you select that recipient, it will mark the lastRead timestamp as the most recent message, essentially marking the entire conversation as "read" even though there are messages above that haven't been seen.
Instead I was hoping to have functionality more like Slack's. in-view or an InteractionObserver could detect when a message has actually come into the viewport.
But what would I store in the state? The ids of each unread message? If so, when I refresh and my app receives all the messages, how does it know any of them are read? I could store the ids of each read message instead but that sounds unwieldy.
Has anyone seen a good design pattern for this?
Solution 1:[1]
We did use IntersectionObserver for exactly the same use case in a chat application.
Pre-requests: The messages have 2 meta-fields:
time(when it was sent)timeSeen(when it was viewed)
The flow is as follows:
- clientA sends a message via the server
- server appends
time = now()andtimeSeen = nullto that message - server saves the messages to the databases
- server forwards the message to clientB
- clientB checks that the message does not have
timeSeen, e.g. it'snull - clientB renders the message inside an
IntersectionObserverwrapped component, which triggers a hook when message is in viewport and passes themessageIdas a param - clientB sends a request to the server to mark the message as seen when requested by the hook
- server marks the message with
messageIdas seen by settingtimeSeen = now() - server sends an update to clientB with meta into on the message with
messageId(timeSeen updated) - clientB sees that timeSeen is not null anymore and does not call the hook on re-renders
I know this sounds a bit of an overkill, but this way you can reliably get the following:
- All messages will have meta on when those where seen
- You can aggregate the unseen counters and lists on the server side based on the meta
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | t1gor |

