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;