Nested ScrollView block parent scroll when started scrolling in child - react-native

I have a React Native app with a parent ScrollView component that wraps other 2 ScrollViews that are siblings. How could I force the parent to only scroll when I've started scrolling from outside the child components?
Current behavior:
When I start scrolling in a child, the child scrolls, but when the list reaches the end, the parent starts scrolling (finger was not removed from the screen, so it was the same scrolling action).
Desired behavior:
When I start scrolling in a child, the child scrolls, but when the list reaches the end, the parent does NOT scroll until I interrupt the current scrolling action and start scrolling again from the area of the parent.

You can't have nested scroll views in the same direction. You can try to improve the UI.
Even for the user this can be confusing.
Send a screenshot, so people can better understand what the goal is.

export const ScrollEnabledContext = createContext(null);
const Parent = () => {
const ref = useRef(null);
const setIsEnabled = (bool) => {
ref.current?.setNativeProps({ scrollEnabled: bool });
};
return (
<ScrollEnabledContext.Provider value={setIsEnabled}>
<ScrollView ref={ref} nestedScrollEnabled={true}>
<ChildScrollView />
</ScrollView>
</ScrollEnabledContext.Provider>
);
};
const ChildScrollView = () => {
const value = useContext(ScrollEnabledContext);
return (
<ScrollView onTouchStart={() => value(false)} onTouchEnd={() => value(true)}>
....
</ScrollView>
);
};

Related

How to make child prevent ScrollView from scrolling

Context:
I have a graph, where I allow a user to "scrub" their finger over the graph, and see a tooltip
This graph is nested inside a ScrollView
<ScrollView>
<Graph>
</ScrollView>
Problem:
I want to "disable" the scroll view when the touch is happening over that graph
I'm not sure how to do that.
function Graph() {
return (
<View onTouchStart={e => /* prevent ScrollView from scrolling */} />
)
}
I know about scrollEnabled on ScrollView, but it won't be easy for me to thread that prop. Is there a way I can just "stop propagation" for that touch event, inside Graph?
onTouchStart={(e) => e.stopPropagation()} does not do the trick
My current solution is the following:
import React, { createContext, useRef } from "react";
import { ScrollView } from "react-native";
export const ScrollEnabledContext = createContext(null);
export default function StoppableScrollView(props) {
const ref = useRef(null);
const setIsEnabled = (bool) => {
ref.current && ref.current.setNativeProps({ scrollEnabled: bool });
};
return (
<ScrollEnabledContext.Provider value={setIsEnabled}>
<ScrollView ref={ref} {...props} />
</ScrollEnabledContext.Provider>
);
}
By using setNativeProps, I prevent a render. I can reference ScrollEnabledContext in the child component to prevent scrolls. A bit brittle, but gets the job done. Would be fantastic if I didn't have to do this, and could use something like stopPropagation

How to use refreshControl on a ScrollView but prevent dragging direction?

I have content that I want to "refresh" with a RefreshControl but I do not want the content to scroll upwards. I was originally under the impression that setting bounces=false and scrollEnabled=true would achieve this.
How about you set a key to the Component, then update it when you want to refresh it.
When a key changes, React see it as a new component. As a result, it will literally refresh the component.
export default () => {
const [refreshKey, setRefreshKey] = React.useState('randomstring');
// call me when refresh required
const refresh = () => {
setRefreshKey(refreshKey + 'randomstring');
};
return (
<ScrollView key={refreshKey}>
{/* Some Awesome Contents */}
</ScrollView>
);
};

Is there a way to get the swipe progress of react-native-swiper?

I would like to have an event like onScroll on the ScrollView but onSwipe on the Swiper. And in this callback I would like to get the "swipe progress" (e.g. swiped 50% to the second item). With all the supported callbacks I can do something when the swipe starts or ends but I also want all the steps in between. How could I do this?
To get the scrolled position of the current view
Swiper component is a ScrollView and you can use its native props with Swiper as well, for your case use onScroll prop to get where the scroll is and the percentage swipped.
const viewWidth = Dimensions.get('window').width;
...
<Swiper
scrollEventThrottle={16}
onScroll={(e) =>
const scrolledPercentage =(e.nativeEvent.contentOffset.x / (viewWidth * (this.currentIndex + 1)));
}
>
...
</Swiper>
.
To get the progress of your passed views
Just get the current index with onIndexChanged props and then calculate your progress with the count of your Views inside of the swiper
<Swiper
style={styles.wrapper}
showsButtons={true}
onIndexChanged={(index) => {
let progress= index/totalNumberOfView
let progressInPercentage = Math.floor(progress*100)
this.setState({
progress,
progressInPercentage
})
}
>

Get current scroll position of FlatList

I have this code:
<FlatList
ref={(x) => (this.flatList = x)}
data={players}
style={this.props.style}
/>
What I need is to save the current scroll position of the FlatList when the user e.g. navigates away.
So something like this.flatList.getCurrentScrollPosition().
Is there such a thing?
FlatList inherits all of ScrollView props. Therefore you can use onScroll.
onScroll = (event) => {
const scrollOffset = event.nativeEvent.contentOffset.y
}

Pass control from a parent view's PanResponder to a child views PanResponder

I got a Component like this:
<View
{ ...this._rangePanResponder.panHandlers } >
<View
{ ...this._sliderPanResponder.panHandlers } />
</View>
When I tap on the child view, it's the parent view's PanResponder which receives and handles the event. I would like to pass the control to the child view.
There is a target property (node id) on the nativeEvent that the PanResponder receives, but I cannot figure out how to map that node id to a view in my component. Is that even possible?
Thanks in advance.
If you tap exactly over the child it should be the child's panResponder who handles it.
However you are passing the slider pans as props so I guess you could set them inside the child's constructor:
constructor (props) {
this.panResponder = props._sliderPanResponder.panHandlers
}