@@ -511,12 +511,12 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
511511 const styles = useStyles ( ) ;
512512 const [ deleted , setDeleted ] = useState < boolean > ( false ) ;
513513 const [ error , setError ] = useState < Error | boolean > ( false ) ;
514- const [ lastRead , setLastRead ] = useState < Date | undefined > ( ) ;
514+ const lastReadRef = useRef < Date | undefined > ( undefined ) ;
515515 const [ thread , setThread ] = useState < LocalMessage | null > ( threadProps || null ) ;
516516 const [ threadHasMore , setThreadHasMore ] = useState ( true ) ;
517517 const [ threadLoadingMore , setThreadLoadingMore ] = useState ( false ) ;
518- const [ channelUnreadStateStore ] = useState ( new ChannelUnreadStateStore ( ) ) ;
519- const [ messageInputHeightStore ] = useState ( new MessageInputHeightStore ( ) ) ;
518+ const [ channelUnreadStateStore ] = useState ( ( ) => new ChannelUnreadStateStore ( ) ) ;
519+ const [ messageInputHeightStore ] = useState ( ( ) => new MessageInputHeightStore ( ) ) ;
520520 // TODO: Think if we can remove this and just rely on the channelUnreadStateStore everywhere.
521521 const setChannelUnreadState = useCallback (
522522 ( data : ChannelUnreadStateStoreType [ 'channelUnreadState' ] ) => {
@@ -690,6 +690,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
690690 return ;
691691 }
692692
693+ if ( event . type === 'message.read_locally' ) {
694+ // When local unread reset happens, the count is already updated in the client state,
695+ // and the preview badge / unread divider are handled elsewhere, so there is nothing
696+ // to copy into channel state here. Thus, we skip it.
697+ return ;
698+ }
699+
693700 if ( event . type === 'message.read' || event . type === 'notification.mark_read' ) {
694701 setReadThrottled ( ) ;
695702 return ;
@@ -703,7 +710,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
703710 useEffect ( ( ) => {
704711 let listener : ReturnType < typeof channel . on > ;
705712 const initChannel = async ( ) => {
706- setLastRead ( new Date ( ) ) ;
713+ lastReadRef . current = new Date ( ) ;
707714 const unreadCount = channel . countUnread ( ) ;
708715 const shouldLoadAtFirstUnread = shouldLoadInitialChannelAtFirstUnreadMessage ( unreadCount ) ;
709716 if ( ! channel || ! shouldSyncChannel ) {
@@ -812,7 +819,25 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
812819 const markReadInternal : ChannelContextValue [ 'markRead' ] = throttle (
813820 async ( options ?: MarkReadFunctionOptions ) => {
814821 const { updateChannelUnreadState = true } = options ?? { } ;
815- if ( ! channel || channel ?. disconnected || ! clientChannelConfig ?. read_events ) {
822+ if ( ! channel || channel ?. disconnected ) {
823+ return ;
824+ }
825+
826+ // When read events are disabled (e.g. livestreams) we cannot mark read on the backend. If the
827+ // client opted into a local unread count, reset it locally instead so the user's "caught up"
828+ // state is reflected without a server round trip.
829+ if ( ! clientChannelConfig ?. read_events ) {
830+ if ( client . options . isLocalUnreadCountEnabled ) {
831+ const event = channel . markReadLocally ( ) ;
832+ if ( updateChannelUnreadState && event && lastReadRef . current ) {
833+ setChannelUnreadState ( {
834+ last_read : lastReadRef . current ,
835+ last_read_message_id : event . last_read_message_id ,
836+ unread_messages : 0 ,
837+ } ) ;
838+ lastReadRef . current = new Date ( ) ;
839+ }
840+ }
816841 return ;
817842 }
818843
@@ -821,13 +846,13 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
821846 } else {
822847 try {
823848 const response = await channel . markRead ( ) ;
824- if ( updateChannelUnreadState && response && lastRead ) {
849+ if ( updateChannelUnreadState && response && lastReadRef . current ) {
825850 setChannelUnreadState ( {
826- last_read : lastRead ,
851+ last_read : lastReadRef . current ,
827852 last_read_message_id : response ?. event . last_read_message_id ,
828853 unread_messages : 0 ,
829854 } ) ;
830- setLastRead ( new Date ( ) ) ;
855+ lastReadRef . current = new Date ( ) ;
831856 }
832857 } catch ( err ) {
833858 console . log ( 'Error marking channel as read:' , err ) ;
@@ -1578,7 +1603,6 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
15781603 hideStickyDateHeader,
15791604 highlightedMessageId,
15801605 isChannelActive : shouldSyncChannel ,
1581- lastRead,
15821606 loadChannelAroundMessage,
15831607 loadChannelAtFirstUnreadMessage,
15841608 loading : channelMessagesState . loading ,
@@ -1590,7 +1614,6 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
15901614 reloadChannel,
15911615 scrollToFirstUnreadThreshold,
15921616 setChannelUnreadState,
1593- setLastRead,
15941617 setTargetedMessage,
15951618 hasPendingInitialTargetLoad,
15961619 targetedMessage,
0 commit comments