1- import React , { useCallback , useEffect , useMemo , useState } from 'react'
1+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react'
22import {
33 View ,
44 LayoutChangeEvent ,
@@ -56,6 +56,7 @@ export const MessagesContainer = <TMessage extends IMessage>(props: MessagesCont
5656
5757 const daysPositions = useSharedValue < DaysPositions > ( { } )
5858 const listHeight = useSharedValue ( 0 )
59+ const contentHeight = useSharedValue ( 0 )
5960 const scrolledY = useSharedValue ( 0 )
6061
6162 const renderTypingIndicator = useCallback ( ( ) => {
@@ -125,6 +126,7 @@ export const MessagesContainer = <TMessage extends IMessage>(props: MessagesCont
125126 ( ! isInverted && lastScrolledY . value < contentOffsetY )
126127
127128 lastScrolledY . value = contentOffsetY
129+ contentHeight . value = contentSizeHeight
128130
129131 if ( isInverted )
130132 if ( contentOffsetY > scrollToBottomOffset ! )
@@ -138,7 +140,39 @@ export const MessagesContainer = <TMessage extends IMessage>(props: MessagesCont
138140 changeScrollToBottomVisibility ( false )
139141 else
140142 changeScrollToBottomVisibility ( false )
141- } , [ isInverted , scrollToBottomOffset , changeScrollToBottomVisibility , isScrollingDown , lastScrolledY , listPropsOnScrollProp ] )
143+ } , [ isInverted , scrollToBottomOffset , changeScrollToBottomVisibility , isScrollingDown , lastScrolledY , contentHeight , listPropsOnScrollProp ] )
144+
145+ // Auto-scroll to the newest message when it arrives in a non-inverted list.
146+ // Inverted lists keep the newest message visible on their own, but a
147+ // non-inverted list appends new messages off-screen at the end (#2612).
148+ // Only scroll when the user is already near the bottom so we don't yank
149+ // them away while they are reading earlier messages.
150+ const latestMessageId = ! isInverted && messages . length > 0
151+ ? messages [ messages . length - 1 ] . _id
152+ : undefined
153+ const previousLatestMessageId = useRef ( latestMessageId )
154+ useEffect ( ( ) => {
155+ if ( isInverted ) {
156+ previousLatestMessageId . current = latestMessageId
157+ return
158+ }
159+
160+ if (
161+ latestMessageId != null &&
162+ latestMessageId !== previousLatestMessageId . current &&
163+ // skip the very first render; initial positioning is handled on layout
164+ previousLatestMessageId . current !== undefined
165+ ) {
166+ const isNearBottom =
167+ contentHeight . value === 0 ||
168+ lastScrolledY . value + listHeight . value >= contentHeight . value - scrollToBottomOffset !
169+
170+ if ( isNearBottom )
171+ doScrollToBottom ( true )
172+ }
173+
174+ previousLatestMessageId . current = latestMessageId
175+ } , [ latestMessageId , isInverted , doScrollToBottom , contentHeight , lastScrolledY , listHeight , scrollToBottomOffset ] )
142176
143177 const restProps = useMemo ( ( ) => {
144178 // eslint-disable-next-line @typescript-eslint/no-unused-vars
0 commit comments