Add ScrollView interceptor directly to children#4331
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a regression in v3 ScrollView/FlatList sticky header behavior introduced by wrapping all ScrollView children into a single logical responder node, which caused stickyHeaderIndices to pin the entire content (or become a no-op for indices > 0). The fix wraps each child individually while preserving the responder-interception behavior via a shared context provider.
Changes:
- Replace the single “logical responder” wrapper with per-child logical wrappers (
interceptScrollViewChildren) sostickyHeaderIndicesmap to the intended children. - Split the interceptor into a context provider (
ScrollViewResponderProvider) plus per-child wrappers (LogicalResponderChild), and apply this in the v3ScrollViewcomponent.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/react-native-gesture-handler/src/v3/components/ScrollViewResponderInterceptor.tsx | Refactors responder interception into a provider + per-child wrapper and adds interceptScrollViewChildren. |
| packages/react-native-gesture-handler/src/v3/components/GestureComponents.tsx | Updates v3 ScrollView to use the provider and wrap children individually via interceptScrollViewChildren. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return ( | ||
| <JSResponderContext value={contextValue}>{children}</JSResponderContext> | ||
| ); |
There was a problem hiding this comment.
I don't think React <19 is a concern
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| {children} | ||
| </View> | ||
| </JSResponderContext> | ||
| <View |
There was a problem hiding this comment.
You're rendering a new non-flattenable view for each ScrollView child. This will measurably impact performance.
Description
#4158 introduced
ScrollViewResponderInterceptor, which wraps allScrollViewchildren in a single logical-responderView. RN'sScrollView.render()resolvesstickyHeaderIndicesagainstReact.Children.toArray(this.props.children)and wraps the child at each sticky index inScrollViewStickyHeader, which counter-translates that child against the scroll offset. With the content collapsed into one child, index0is the entire content — so everything gets pinned. Indices > 0 were a silent no-op (they pointed past the single child).To fix this, we now wrap each child separately.
Fixes #4328
Test plan
Tested on the following code: