fix: scroll to the model end offset in doMaintainScrollAtEnd instead of native scrollToEnd#471
Open
mperonnet wants to merge 1 commit into
Open
Conversation
doMaintainScrollAtEnd used the native scrollToEnd command, whose target is computed natively from contentSize + contentInset at execution time. When a contentInset update is still in flight — e.g. an inset driven by Reanimated animatedProps on Fabric, which lands via an asynchronous shadow-tree commit (and is paused during React commits, i.e. exactly when a new message was just appended) — the native command stops short of the model's end position by exactly the pending inset, and nothing corrects it afterwards: checkAtBottom subtracts insetEnd back out, so the clamped position still reads as isAtEnd. Dispatch scrollTo at the model-computed end offset instead (getContentSize - scrollLength), consistent with every other scroll path in the library (scrollToEnd/scrollToIndex already target model-computed offsets that include getContentInsetEnd). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
doMaintainScrollAtEnddispatches the nativescrollToEndcommand (for the non-RTL case), whose target offset is computed natively fromcontentSize + contentInsetat execution time (RCTScrollViewComponentView:offsetY = contentSize.height - bounds.height + contentInset.bottom).This desyncs from the library's own model whenever a
contentInsetupdate is still in flight. The concrete case: theKeyboardAwareLegendListintegration withreact-native-keyboard-controller, where the composer/keyboard clearance is acontentInsetdriven by ReanimatedanimatedProps. On Fabric that inset lands via an asynchronous shadow-tree commit — and Reanimated commits are paused while a React commit is in flight, which is exactly the momentmaintainScrollAtEndfires (a new message was just appended). The native command then computes its target with the stale inset and stops exactlyinsetEndshort of the model's end position.Nothing self-corrects afterwards:
checkAtBottomcomputesdistanceFromEnd = contentSize - scroll - scrollLength - insetEndwithcontentSizeitself includinginsetEnd, so the clamped-short position (real content bottom at viewport bottom, last rows hidden behind the composer) still reads asisAtEnd === true.Observed symptom in production (RN 0.85.3, Reanimated 4.3.1, New Arch, keyboard-controller 1.21.11, list 3.0.4): on send with the keyboard open, content drops behind the keyboard and then visibly re-adjusts ~100 ms later when the iOS retry (
shouldRetryUnalignedEndScroll) re-dispatches after the inset commit lands; on mount, conversations intermittently rest with the last message hidden behind the composer. Verified on device by reading the native inset from scroll events (nativeEvent.contentInset): the model'scontentInsetOverride.bottomwas 86 while the native inset was still 0 at the time the end scroll executed.Fix
Dispatch
scrollToat the model-computed end offset (getContentSize(ctx) - scrollLength, clamped to 0) instead of the nativescrollToEndcommand. This is consistent with every other scroll path in the library —scrollToEnd/scrollToIndexalready target model-computed offsets that includegetContentInsetEnd— and makesmaintainScrollAtEndindependent of native inset timing. The RTL-horizontal branch already worked this way (logical offset → native offset); this aligns the default branch with it.Additional context
I was able to reproduce and patch this locally, and the fix resolves the issue in my testing.
The diagnosis was done with Claude's assistance. Sharing in case it's useful; feel free to close the issue if this analysis doesn't match the intended behavior.