Skip to content

fix: add option to override date separator calculation - #3807

Closed
isekovanic wants to merge 3 commits into
developfrom
refactor/add-option-to-override-date-separator-calc
Closed

isekovanic wants to merge 3 commits into
developfrom
refactor/add-option-to-override-date-separator-calc

Conversation

@isekovanic

Copy link
Copy Markdown
Contributor

🎯 Goal

This PR fixes an issue where channels that get a system message at a day boundary render no date separators at all. Should resolve this Zendesk ticket.

The boundary gets consumed twice. MessageWrapper computes the correct separator for the system message and then drops it, because the type === 'system' branch has no separator slot. The next regular message then compares against that system message, sees the same day, and renders nothing either. This has been the case since at least v8.9.2.

Fixing it also means picking a rule, and no single rule suits everyone, for example some integrations might want days containing only a session marker left undated, which can't be answered from a message's neighbours. So this PR fixes the default and adds a getDateSeparators prop for rules the default can't express.

🛠 Implementation details

A separator goes above the first row of each day, whatever type it is, so everything below a separator belongs to the day it names. There is no message-type special-casing left anywhere in the separator path.

The rule is neighbour-local, so each row derives its own separator from previousMessage exactly as before so no list-wide pass. useDateSeparatorDates returns undefined unless an override is supplied, and there's a test pinning that.

getDateSeparators (new, on Channel) receives every loaded message oldest-first and returns { messageId: Date }:

getDateSeparators?: (params: {
  messages: LocalMessage[];
  hideDateSeparators?: boolean;
}) => Record<string, Date>;

Only when it's set does the list resolve separators up front and hand each row its answer. getDefaultDateSeparators is exported so integrators can build on the SDK's rule instead of restating it. Two details worth knowing when reviewing:

  • The list resolving undefined for a row means "no separator here" and is distinct from "no override" so the row reads getDateSeparators from context to tell them apart. Collapsing the two would make an override that suppresses everything indistinguishable from no override at all
  • A returned Date that's value-equal to the message's own created_at is collapsed back to that instance, so an override can't accidentally break row memoization by constructing new Dates.

Also here: getDateSeparatorValue compares an integer day key instead of formatting two strings (~13× cheaper, measured), and the sticky header no longer takes its date from a system message.

Not breaking. MessageWrapper and buildMessageListWithNeighbours aren't exported. useMessageGroupStyles is public and still derives nextMessageDateSeparatorDate itself when a caller omits it — contract-tested.

🎨 UI Changes

Android
iOS

🧪 Testing

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@Stream-SDK-Bot

Stream-SDK-Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

SDK Size

title develop branch diff status
js_bundle_size 2031 KB 2033 KB +2848 B 🔴

@isekovanic
isekovanic marked this pull request as draft September 15, 2026 20:05
@isekovanic isekovanic closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants