Open sfalihi opened 3 years ago
@sfalihi No!! Not at the moment
Going to keep this open to see if there is interest from more users for this!!
Yep I am very interrested in this feature...
Also interested.
I'm also interested in this feature for a horizontal time-based calendar-like component that allows users to scroll back and forth infinately.
Definitely interested as well
Has this issue been resolved? :(
Yes I need this feature ✌🏽
Interested!
just write horizontal in flatlist it will work
just write horizontal in flatlist it will work
No it will not. onEndReached and onStartReached are not firing then.
Also interested 👍
yes it would be nice to have it 👍
I'm also interested in this feature for a horizontal time-based calendar-like component that allows users to scroll back and forth infinately. @Findiglay
I am also facing the same problem. Did you by any chance find out how to have a bidirectional scroll in a calendar-like component?
@Nautman @Findiglay or anyone interested in a calendar-like component with horizontal bidirectional infinite scroll*,
you can check this library https://github.com/hoangnm/react-native-week-view for a week view (disclaimer: I'm a maintainer), plus we are currently working on a month view: https://github.com/hoangnm/react-native-week-view/issues/95#issuecomment-1160885796 :smiley:
(*) for now is implemented with a FlatList plus some workarounds
@vishalnarkhede please, man, add the horizontal feature!
here is it !
/* eslint-disable @typescript-eslint/no-unused-vars */
// noinspection JSUnusedLocalSymbols
/**
* https://github.com/GetStream/react-native-bidirectional-infinite-scroll
*/
// noinspection JSUnusedGlobalSymbols
import React, { MutableRefObject, useRef, useState } from 'react';
import {
ActivityIndicator,
FlatList as FlatListType,
FlatListProps,
ScrollViewProps,
StyleSheet,
View,
} from 'react-native';
import { FlatList } from '@stream-io/flat-list-mvcp';
const styles = StyleSheet.create({
indicatorContainer: {
paddingVertical: 5,
width: '100%',
},
});
export type Props<T> = Omit<FlatListProps<T>, 'maintainVisibleContentPosition'> & {
ref?: ((instance: FlatListType<T> | null) => void) | MutableRefObject<FlatListType<T> | null> | null;
/**
* Called once when the scroll position gets close to end of list. This must return a promise.
* You can `onEndReachedThreshold` as distance from end of list, when this function should be called.
*/
onEndReached: () => Promise<void>;
/**
* Called once when the scroll position gets close to begining of list. This must return a promise.
* You can `onStartReachedThreshold` as distance from beginning of list, when this function should be called.
*/
onStartReached: () => Promise<void>;
/** Color for inline loading indicator */
activityIndicatorColor?: string;
/**
* Enable autoScrollToTop.
* In chat type applications, you want to auto scroll to bottom, when new message comes it.
*/
enableAutoscrollToTop?: boolean;
/**
* If `enableAutoscrollToTop` is true, the scroll threshold below which auto scrolling should occur.
*/
autoscrollToTopThreshold?: number;
/** Scroll distance from beginning of list, when onStartReached should be called. */
onStartReachedThreshold?: number;
/**
* Scroll distance from end of list, when onStartReached should be called.
* Please note that this is different from onEndReachedThreshold of FlatList from react-native.
*/
onEndReachedThreshold?: number;
/** If true, inline loading indicators will be shown. Default - true */
showDefaultLoadingIndicators?: boolean;
/** Custom UI component for header inline loading indicator */
HeaderLoadingIndicator?: React.ComponentType;
/** Custom UI component for footer inline loading indicator */
FooterLoadingIndicator?: React.ComponentType;
/** Custom UI component for header indicator of FlatList. Only used when `showDefaultLoadingIndicators` is false */
ListHeaderComponent?: React.ComponentType;
/** Custom UI component for footer indicator of FlatList. Only used when `showDefaultLoadingIndicators` is false */
ListFooterComponent?: React.ComponentType;
};
/**
* Note:
* - `onEndReached` and `onStartReached` must return a promise.
* - `onEndReached` and `onStartReached` only get called once, per content length.
* - maintainVisibleContentPosition is fixed, and can't be modified through props.
* - doesn't accept `ListFooterComponent` via prop, since it is occupied by `FooterLoadingIndicator`.
* Set `showDefaultLoadingIndicators` to use `ListFooterComponent`.
* - doesn't accept `ListHeaderComponent` via prop, since it is occupied by `HeaderLoadingIndicator`
* Set `showDefaultLoadingIndicators` to use `ListHeaderComponent`.
*/
export const BidirectionalFlatList = React.forwardRef(
<T extends any>(
props: Props<T>,
ref: ((instance: FlatListType<T> | null) => void) | MutableRefObject<FlatListType<T> | null> | null,
) => {
const {
activityIndicatorColor = 'black',
autoscrollToTopThreshold = 100,
data,
enableAutoscrollToTop,
FooterLoadingIndicator,
HeaderLoadingIndicator,
ListHeaderComponent,
ListFooterComponent,
onEndReached = () => Promise.resolve(),
onEndReachedThreshold = 10,
onScroll,
onStartReached = () => Promise.resolve(),
onStartReachedThreshold = 10,
showDefaultLoadingIndicators = true,
horizontal = false,
} = props;
const [onStartReachedInProgress, setOnStartReachedInProgress] = useState(false);
const [onEndReachedInProgress, setOnEndReachedInProgress] = useState(false);
const onStartReachedTracker = useRef<Record<number, boolean>>({});
const onEndReachedTracker = useRef<Record<number, boolean>>({});
const onStartReachedInPromise = useRef<Promise<void> | null>(null);
const onEndReachedInPromise = useRef<Promise<void> | null>(null);
const maybeCallOnStartReached = () => {
// If onStartReached has already been called for given data length, then ignore.
if (data?.length && onStartReachedTracker.current[data.length]) {
return;
}
if (data?.length) {
onStartReachedTracker.current[data.length] = true;
}
setOnStartReachedInProgress(true);
const p = () => {
return new Promise<void>(resolve => {
onStartReachedInPromise.current = null;
setOnStartReachedInProgress(false);
resolve();
});
};
if (onEndReachedInPromise.current) {
onEndReachedInPromise.current.finally(() => {
onStartReachedInPromise.current = onStartReached().then(p);
});
} else {
onStartReachedInPromise.current = onStartReached().then(p);
}
};
const maybeCallOnEndReached = () => {
// If onEndReached has already been called for given data length, then ignore.
// console.log(
// '-- maybeCallOnEndReached',
// data?.length,
// data?.length ? onEndReachedTracker.current[data.length] : false,
// );
if (data?.length && onEndReachedTracker.current[data.length]) {
return;
}
if (data?.length) {
onEndReachedTracker.current[data.length] = true;
}
setOnEndReachedInProgress(true);
const p = () => {
return new Promise<void>(resolve => {
onStartReachedInPromise.current = null;
setOnEndReachedInProgress(false);
resolve();
});
};
if (onStartReachedInPromise.current) {
onStartReachedInPromise.current.finally(() => {
onEndReachedInPromise.current = onEndReached().then(p);
});
} else {
onEndReachedInPromise.current = onEndReached().then(p);
}
};
const handleScroll: ScrollViewProps['onScroll'] = event => {
// Call the parent onScroll handler, if provided.
onScroll?.(event);
const offset = event.nativeEvent?.contentOffset?.x;
const visibleLength = horizontal
? event.nativeEvent.layoutMeasurement.width
: event.nativeEvent.layoutMeasurement.height;
const contentLength = horizontal ? event.nativeEvent.contentSize.width : event.nativeEvent.contentSize.height;
// Check if scroll has reached either start of end of list.
const isScrollAtStart = offset < onStartReachedThreshold;
const isScrollAtEnd = contentLength - visibleLength - offset < onEndReachedThreshold;
// console.log('-- isScrollAtStart=', isScrollAtStart, 'isScrollAtEnd=', isScrollAtEnd, 'horizontal=', horizontal);
if (isScrollAtStart) {
maybeCallOnStartReached();
}
if (isScrollAtEnd) {
maybeCallOnEndReached();
}
};
const renderHeaderLoadingIndicator = () => {
if (!showDefaultLoadingIndicators) {
if (ListHeaderComponent) {
return <ListHeaderComponent />;
} else {
return null;
}
}
if (!onStartReachedInProgress) {
return null;
}
if (HeaderLoadingIndicator) {
return <HeaderLoadingIndicator />;
}
return (
<View style={styles.indicatorContainer}>
<ActivityIndicator size={'small'} color={activityIndicatorColor} />
</View>
);
};
const renderFooterLoadingIndicator = () => {
if (!showDefaultLoadingIndicators) {
if (ListFooterComponent) {
return <ListFooterComponent />;
} else {
return null;
}
}
if (!onEndReachedInProgress) {
return null;
}
if (FooterLoadingIndicator) {
return <FooterLoadingIndicator />;
}
return (
<View style={styles.indicatorContainer}>
<ActivityIndicator size={'small'} color={activityIndicatorColor} />
</View>
);
};
return (
<>
<FlatList<T>
{...props}
ref={ref}
progressViewOffset={50}
ListHeaderComponent={renderHeaderLoadingIndicator}
ListFooterComponent={renderFooterLoadingIndicator}
onEndReached={null}
onScroll={handleScroll}
/*
// v0.73.0-rc.2
// https://github.com/facebook/react-native/commit/1a1a79871b2d040764537433b431bc3b416904e3
maintainVisibleContentPosition={{
autoscrollToTopThreshold: enableAutoscrollToTop ? autoscrollToTopThreshold : undefined,
minIndexForVisible: 1,
}}*/
/>
</>
);
},
) as unknown as BidirectionalFlatListType;
type BidirectionalFlatListType = <T extends any>(props: Props<T>) => React.ReactElement;
it throw error with horizontal=true does it support horizontal flatlist?