React double execution - react-native

I'm working on a infinite scroll on scrollview, to do this I use Refrescontrol and that function:
handleOnScroll(e){
let yOffset = e.nativeEvent.contentOffset.y;
if (this.state.hWall && this.state.hWall - this.state.hScreen - yOffset <= 0 && !this.state.isRefreshing){
this.setState({ isRefreshing : true }, () => this.getPosts('more') );
}
}
Now the problem is that Alway I have two this.getPosts('more') calls, how to prevents double call before the getPosts action are completed?

The problem is that setState is asyncronous. Try using this.isRefresing to control whether there is a refresing ongoing or not.
handleOnScroll(e){
let yOffset = e.nativeEvent.contentOffset.y;
if (this.state.hWall && this.state.hWall - this.state.hScreen - yOffset <= 0 && !this.isRefreshing){
this.isRefresing = true; //this is syncronous
//setState is still required if you want to update your UI
this.setState({ isRefreshing : true }, () => this.getPosts('more') );
}
}
Furthermore, if you trade your scroll view for a flatList, you can use the onEndReached callback to fetch more data when the list is about to end.

Related

How to detect if FlatList scroll is triggered by gesture or by scrollToOffset method?

How can you tell if a flatlist scroll is triggered by a user gesture or by a scrollToOffset or scrollToIndex method?
I search for something like that... (not working)
const onScroll=(event, isGesture)=>{
if(!isGesture)return
}
<FlatList onScroll={onScroll}/>
You can create a ref that you toggle before you call scrollToOffset or scrollToIndex, and then toggle again after the scroll animatino completes.
let isGesture = useRef(true).current;
Check this ref's value in your onScroll function to determine what to do.
const onScroll=e=>{
if(!isGesture){
console.log('inorganic scrolling!')
return
}
console.log('Organic scrolling!')
// do your scroll stuff
}
The biggest issue you will encounter will be figuring out how long to wait before toggling the ref back to its default value. If you wait too long, then you may miss user scroll events, and if you wait too shortly, you will capture the end of the scrollTo animation. Sadly, FlatList doesnt reveal anything about its scroll animation duration so you kinda have to guess:
const {width, height} = useWindowDimensions();
let flatListRef = useRef({}).current;
let currentIndex = useRef(0).current;
const listHeight = height;
// simulate inorganic scroll
const randomScroll = ()=>{
// set isGesture to false before doing inorganic scrolling
isGesture = false;
let index = Math.floor(getRandom(0,data.length));
flatListRef?.scrollToIndex({index,viewPosition:0});
// calculate time to wait before setting isGesture back to true
let itemsToPass = Math.abs(currentIndex - index);
let scrollDist = itemsToPass*listHeight;
// i found that scrolling 613 pixels take about 750ms
const baseDist = 613
const baseDuration = 750;
// i doubt this is the formula used to calculate
// flatlist animation duration but it kinda works
const estimatedAnimationDuration = Math.min(
baseDuration**(1+(baseDist/baseDuration))**(itemsToPass/100),
// animation usually doesnt exceeds 1500ms
1500
);
console.log('Passing',itemsToPass,'items will take',estimatedAnimationDuration)
// wait for animation to finish
setTimeout(()=>{
isGesture=true
},estimatedAnimationDuration)
currentIndex = index
}
Here's an example
EDIT
Alternatively, you could disable the scroll animation and provide a small amount of time for the offset:
const randomScroll = () => {
// set isGesture to false before doing inorganic scrolling
isGesture = false;
let index = Math.floor(getRandom(0, data.length));
const scrollConfig = {
index,
viewPosition: 0,
animated:false,
}
flatListRef?.scrollToIndex(scrollConfig);
// calculate time to wait before setting isGesture back to true
let itemsToPass = Math.abs(currentIndex - index);
let scrollDist = itemsToPass * listHeight;
// i found that scrolling 613 pixels take about 750ms
const baseDist = 613;
const baseDuration = 750;
// i doubt this is the formula used to calculate
// flatlist animation duration but it kinda works
const estimatedAnimationDuration = scrollConfig.animated
? Math.min(
baseDuration ** ((1 + baseDist / baseDuration) ** (itemsToPass / 100)),
// animation usually doesnt exceeds 1500ms
1500)
: 200
scrollConfig.animated && console.log(
'Passing',
itemsToPass,
'items will take',
estimatedAnimationDuration
);
// wait for animation to finish
setTimeout(() => {
isGesture = true;
}, estimatedAnimationDuration);
currentIndex = index;
};
Demo this here
onMomentumScrollEnd only triggeres on user Scroll
const onMomentumScrollEnd=(event)=>{
}
<FlatList onMomentumScrollEnd={onScroll}/>

React-native flatlist to keep scrolling while touching view

I am trying to implement a drag and drop solution in react-native and I want to make my flatlist scroll if I drag an item in the upper 10 percent and bottom 10 percent of the view. So far the only way i can get it to happen is by calling a function that recursively dispatches a this._flatList.scrollToOffset({ offset, animated: false });. The recursive call stops when a certain condition is met but the scrolling effect is choppy. Any advice on making that smoother?
// on move pan responder
onPanResponderMove: Animated.event([null, { [props.horizontal ? 'moveX' : 'moveY']: this._moveAnim }], {
listener: (evt, gestureState) => {
const { moveX, moveY } = gestureState
const { horizontal } = this.props
this._move = horizontal ? moveX : moveY;
const { pageY } = evt.nativeEvent;
const tappedPixel = pageY;
const topIndex = Math.floor(((tappedPixel - this._distanceFromTop + this._scrollOffset) - this._containerOffset) / 85);
const bottomIndex = Math.floor(((tappedPixel + (85 - this._distanceFromTop) + this._scrollOffset) - this._containerOffset) / 85);
this.setTopAndBottom(topIndex, bottomIndex);
this.scrolling = true;
this.scrollRec();
}
}),
// recursive scroll function
scrollRec = () => {
const { activeRow } = this.state;
const { scrollPercent, data, } = this.props;
const scrollRatio = scrollPercent / 100;
const isLastItem = activeRow === data.length - 1;
const fingerPosition = Math.max(0, this._move - this._containerOffset);
const shouldScrollUp = fingerPosition < (this._containerSize * scrollRatio); // finger is in first 10 percent
const shouldScrollDown = !isLastItem && fingerPosition > (this._containerSize * (1 - scrollRatio)) // finger is in last 10
const nextSpacerIndex = this.getSpacerIndex(this._move, activeRow);
if (nextSpacerIndex >= this.props.data.length) { this.scrolling = false; return this._flatList.scrollToEnd(); }
if (nextSpacerIndex === -1) { this.scrolling = false; return; }
if (shouldScrollUp) this.scroll(-20);
else if (shouldScrollDown) this.scroll(20);
else { this.scrolling = false; return; };
setTimeout(this.scrollRec, 50);
}
/
Yeah, pass an options object to your Animated.event with your listener with useNativeDriver set to true
Animated.event([
null,
{ [props.horizontal ? "moveX" : "moveY"]: this._moveAnim },
{
listener: ...,
useNativeDriver: true
}
]);
Should make things significantly smoother.
edit: I feel I should add that there is probably a saner way to accomplish this, are you only using a FlatList because of it's "infinite" dimensions?

A good way to detect swipe right and show the animation when dragging the

In my detail scroll view, I detect the gesture and go back to previous navigation.
However, The go back will happen immediately. Unlike most app will have a animation to peek/preview the previous view.
How to enable this in react-native?
Also, the swipe right detect is not so user-friendly.
Here's my algorithm.
Any idea to make it more smoothly?
onPanResponderMove(event, gestureState) {
if(event.nativeEvent.changedTouches.length===1){
// x 位移
let diffX = gestureState.dx - this.lastPositionX
if (this.lastPositionX === null) {
diffX = 0
}
// y 位移
let diffY = gestureState.dy - this.lastPositionY
if (this.lastPositionY === null) {
diffY = 0
}
if(diffX > 5 && Math.abs(diffY) < 10 ){
this.props.navigation.goBack()
}
// 保留这一次位移作为下次的上一次位移
this.lastPositionX = gestureState.dx
this.lastPositionY = gestureState.dy
}
}

react native - updating progress in the UI

I'm trying to process some data in react native javascript while showing a progress percentage in the UI.
The naive implementation is something like:
readData(){
let i=0
let len = elements.length
for (; i < len; i++){
this.setState({progress:i/len});
this.process(elements[i]) // take some time
}
}
This, of course, will not work, as react native batch all setstate calls and call the last one.
Anyone has an idea how it should work?
Thanks!
If you want to update state after the progress you need to use callbacks or Promise.
Example
process(element, index, callback) {
setTimeOut(() => {
// some functions
if(callback && typeof callback === 'function') callback();
}, (10000* index))
}
readData(){
let i=0
let len = elements.length
for (; i < len; i++){
this.process(elements[i], i, () => {
const progress = i/len;
this.setState({progress});
});
}
}

Titanium Infinite Scroll : Not working

I am trying to add views to my scroll view when it reaches 40% scroll. This is the way I am doing it :
scrollView.add(//add first 10 initial containerView's);
var triggerScroll = true;
var scrollPercentage = 0;
scrollView.addEventListener('scroll', function(e) {
var devHeight = Ti.Platform.displayCaps.platformHeight;
var currPos = scrollView.contentOffset.y;
if(currPos > devHeight){
currPos = currPos - devHeight;
}
scrollPercentage = (currPos)/devHeight * 100;
if(scrollPercentage > 40 && triggerScroll){
triggerScroll = false;
var containerView = myapp.createMyView();
scrollView.add(containerView);
}
//reset scroll to true after the offset reaches end of the screen, so that the
//'scroll' event listener only gets called ONCE every time it crosses 40%
if(scrollPercentage > 101){
triggerScroll = true;
}
});
But its just not working. I am trying to support infinite scroll in my vertical scroll view. Any idea whats going wrong ?
I use the module below when working with infinite scrolling. It use a TableView, but I would think you can apply it to a ScrollView as well. You need to pass in a function that will be called when the TableView register that more content should be loaded.
When you have finished loading you must call the loadingDone-function in order to enable the TableView to initiate another loading sequence.
The value m_bNoDataFound ensure that the loading sequence is not initiated, when there is no more data to fill into the list.
You can alter the offsets (currently 0.75 for Android and 0.90 for iOS) if want the loading sequence to be initiated sooner or later during scroll.
function TableView( onLoad ) {
var isAndroid = Ti.Platform.osname === 'android' ? true : false;
var m_bNoDataFound = false;
var m_nLastDistance = 0;
var m_bPulling = false;
var m_bLoading = false;
var table = Ti.UI.createTableView( {
height : Ti.UI.FILL
} );
table.addEventListener( 'scroll', function( evt ) {
//Scroll to load more data
if( !m_bNoDataFound ) {
if( isAndroid ) {
if( !m_bLoading && ( evt.firstVisibleItem + evt.visibleItemCount ) >= ( evt.totalItemCount * 0.75 ) ) {
onLoad( true );
m_bLoading = true;
}
}
else {
var nTotal = evt.contentOffset.y + evt.size.height;
var nEnd = evt.contentSize.height;
var nDistance = nEnd - nTotal;
if( nDistance < m_nLastDistance ) {
var nNearEnd = nEnd * 0.90;
if( !m_bLoading && ( nTotal >= nNearEnd ) ) {
onLoad( true );
m_bLoading = true;
}
}
m_nLastDistance = nDistance;
}
}
} );
function m_fLoadingDone( a_bNoDataFound ) {
m_bNoDataFound = a_bNoDataFound;
if( m_bLoading )
setTimeout( function( ) {
m_bLoading = false;
}, 250 );
}
return {
table : table,
loadingDone : m_fLoadingDone
};
};
module.exports = TableView;
When integrating an infinite scroll within a scrollview, there are some important things you have to consider:
1. scroll event is triggered a lot: try to throttle your scroll event callback using underscoreJS.
Throttle creates and returns a new, throttled version of the passed function, that, when invoked repeatedly, will only actually call the original function at most once per every wait milliseconds. Useful for rate-limiting events that occur faster than you can keep up with. See the underscorejs documentation for more.
2. Default and system units on Android vs iOS: The size of a view on Android uses a different display unit than coordinates of a view. This mismatch in units will cause incorrect calculation of your trigger for the infinite scroll. To solve this, you have to get and set the default unit yourself. The solution can be found in this widget (see getDefaultUnit()): https://github.com/FokkeZB/nl.fokkezb.color/blob/master/controllers/widget.js
3. The ti-infini-scroll can help you with this: this library creates a wrapper around the default Titanium ScrollView. This wrapper contains the calculation of the end of scroll (your trigger for updating/getting new data). When using this library, don't forget to implement bullet number 2.
https://github.com/goodybag/ti-infini-scroll