Skip to content

Commit 0994931

Browse files
committed
fix: auto-scroll to new message when inverted is false (#2612)
A non-inverted message list appends new messages off-screen at the end and never scrolled to reveal them (inverted lists keep the newest message visible on their own). Track the latest message and, when it changes in a non-inverted list, scroll to the bottom - but only when the user is already near the bottom, so it doesn't yank them away while reading earlier messages.
1 parent 44802f3 commit 0994931

1 file changed

Lines changed: 36 additions & 2 deletions

File tree

src/MessagesContainer/index.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useEffect, useMemo, useState } from 'react'
1+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
22
import {
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

Comments
 (0)