Reputation: 23563
My project is React Native but this is a general React question. I need to get the Y position of a View relative to the root element (so I can do some calculations against the pageY
event from a PanResponder
). This View is in a parent component with a ScrollView and I need this value to update as you scroll as it will have changed.
I can do the measurement in the View with onLayout
and the measure
callback so my question is how should I recalculate when scrolling occurs? I have a solution that works but I'm not sure if it's ideal in terms of performance or if I've gone about it in a needlessly verbose way.
My solution is to use the onScroll
event to set a piece of state which I pass down to the View, and then use it as a dependancy in useEffect
which re-fires the calculation. Im not sure if it's efficient as the View component doesn't actually use this value, it only needs the event that the scroll position has changed. So the View is currently re-rendering every-time you scroll:
export default function App() {
const [scrollPosition, setScrollPosition] = React.useState(null);
return (
<ScrollView
onScroll={(e) => {
setScrollPosition(e.nativeEvent.contentOffset.y);
}}
scrollEventThrottle={16}
>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<ContainerFunctional scrollPosition={scrollPosition} />
</ScrollView>
);
}
const ContainerFunctional = ({ scrollPosition }) => {
const items = new Array(100).fill("");
const marker = React.useRef(null);
React.useEffect(() => {
marker.current.measure((x, y, width, height, pageX, pageY) => {
if (scrollPosition !== null) {
console.log("on scroll ", pageY);
}
});
}, [scrollPosition]);
return (
<View
ref={marker}
onLayout={() => {
if (marker.current) {
marker.current.measure((x, y, width, height, pageX, pageY) => {
console.log("initial ", pageY);
});
}
}}
>
{items.map((item, index) => {
return <Text key={index}>{index}</Text>;
})}
</View>
);
};
Upvotes: 1
Views: 3045
Reputation: 2173
One solution to avoid re-rendering is to use a ref.
export default function App() {
const container = React.useRef(null);
return (
<ScrollView
onScroll={(e) => {
container.current.onScroll();
}}
scrollEventThrottle={16}
>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<Text>Text</Text>
<ContainerFunctional ref={container} />
</ScrollView>
);
}
const ContainerFunctional = React.forwardRef((_, ref) => {
const items = new Array(100).fill("");
const marker = React.useRef(null);
React.useImperativeHandle(ref, () => ({
onScroll: () =>{
marker.current.measure((x, y, width, height, pageX, pageY) => {
console.log("on scroll ", pageY);
});
}
}));
return (
<View
ref={marker}
onLayout={() => {
if (marker.current) {
marker.current.measure((x, y, width, height, pageX, pageY) => {
console.log("initial ", pageY);
});
}
}}
>
{items.map((item, index) => {
return <Text key={index}>{index}</Text>;
})}
</View>
);
});
Upvotes: 5