From 813923d7908040f866fa4c93304dc8793f3d8818 Mon Sep 17 00:00:00 2001 From: Gaurav Kumar <36784501+gauravkumar1007@users.noreply.github.com> Date: Wed, 22 Apr 2026 17:02:53 +0530 Subject: [PATCH] Update withIO.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. React.forwardRef – the HOC returns a forward-ref component. 2. captureScrollerRef – the inner list’s ref callback sets this.scroller.current and forwards the outer ref (Animated’s merged ref + your listRef) to that same FlatList instance. 3. forwardedRef is stripped from props before {...restProps} so it is not passed down to FlatList. 4. Wrapper View keeps collapsable={false} so the IO root view is not flattened away on Android. Intersection logic still uses nativeRef on the wrapper View for root.node; scroll + Animated use the list instance. --- lib/withIO.js | 86 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/lib/withIO.js b/lib/withIO.js index ef91e8e..547db0a 100644 --- a/lib/withIO.js +++ b/lib/withIO.js @@ -1,20 +1,25 @@ -import React, { PureComponent, createRef, } from 'react'; +import React, { createRef, forwardRef, PureComponent } from 'react'; +import { findNodeHandle, View } from 'react-native'; import IOContext from './IOContext'; import IOManager from './IOManager'; + function withIO(Comp, methods) { - const IOScrollableComponent = class extends PureComponent { - node; + class IOScrollableComponent extends PureComponent { + nativeRef; scroller; root; manager; contextValue; + constructor(props) { super(props); + const self = this; this.scroller = createRef(); + this.nativeRef = createRef(); this.root = { get node() { - return self.node; + return self.nativeRef.current; }, get horizontal() { return !!self.props.horizontal; @@ -41,69 +46,114 @@ function withIO(Comp, methods) { zoomScale: 1, }, }; + const manager = new IOManager({ root: this.root, get rootMargin() { return self.props.rootMargin; }, - get threshold() { - return self.props.threshold; - }, }); + this.manager = manager; this.contextValue = { manager, }; - } - componentDidMount() { - this.node = - this.scroller.current?.getNativeScrollRef?.() || this.scroller.current; + methods.forEach((method) => { this[method] = (...args) => { this.scroller.current?.[method]?.(...args); }; }); + + // Forward the outer ref (including Animated.createAnimatedComponent's + // merged ref) to the real FlatList/ScrollView so native scroll events + // attach to the scroll view, not the IO wrapper instance. + this.captureScrollerRef = (node) => { + this.scroller.current = node; + const outerRef = this.props.forwardedRef; + if (typeof outerRef === 'function') { + outerRef(node); + } else if (outerRef != null) { + outerRef.current = node; + } + }; + } + + componentDidMount() { + this.nativeNode = findNodeHandle(this.nativeRef.current); } + handleContentSizeChange = (width, height) => { const { contentSize } = this.root.current; + if (width !== contentSize.width || height !== contentSize.height) { this.root.current.contentSize = { width, height }; if (width > 0 && height > 0 && this.root.onLayout) { this.root.onLayout(); } } + const { onContentSizeChange } = this.props; if (onContentSizeChange) { onContentSizeChange(width, height); } }; + handleLayout = (event) => { - const { nativeEvent: { layout }, } = event; + const { + nativeEvent: { layout }, + } = event; const { layoutMeasurement } = this.root.current; - if (layoutMeasurement.width !== layout.width || - layoutMeasurement.height !== layout.height) { + + if (layoutMeasurement.width !== layout.width || layoutMeasurement.height !== layout.height) { this.root.current.layoutMeasurement = layout; } + const { onLayout } = this.props; if (onLayout) { onLayout(event); } }; + handleScroll = (event) => { this.root.current = event.nativeEvent; + if (this.root.onScroll) { this.root.onScroll(this.root.current); } + const { onScroll } = this.props; if (onScroll) { onScroll(event); } }; + render() { - return (React.createElement(IOContext.Provider, { value: this.contextValue }, - React.createElement(Comp, { scrollEventThrottle: 16, ...this.props, ref: this.scroller, onContentSizeChange: this.handleContentSizeChange, onLayout: this.handleLayout, onScroll: this.handleScroll }))); + const { forwardedRef, ...restProps } = this.props; + + return ( + + + + + + ); } - }; - return IOScrollableComponent; + } + + const ForwardedIO = forwardRef((props, ref) => ); + + const name = Comp.displayName || Comp.name || 'Scrollable'; + ForwardedIO.displayName = `IO(${name})`; + + return ForwardedIO; } + export default withIO;