How to detect user finger lift off when scrolling [Flatlist] - reactjs

How to detect user finger lift off after scrolling? I've added pan responder with handlers on release is not firing.

With 'onMomentumScrollBegin' and 'onMomentumScrollEnd' props you can decide what to happen when the user scrolls the FlatList with hand.
userLiftedFingerOffScreen() {
// do whatever
}
render() =>
<FlatList
data={listData}
renderItem={({item}) => <SomeItem item={item} />}
onMomentumScrollEnd={this.userLiftedFingerOffScreen}
/>
I myself used this to make a slide show with Flatlist and it's pagination ability and I needed a non-automatic scrolling and onMomentumScroll was the solution.

If you just want to know WHEN the user has lifted their finger off the screen after scroll, you can use the onScrollEndDrag Function. FlatList iinherits props from ScrollView so all of ScrollView's props are accessible in a FlatList. You shouldn't need to use pan responder for this.
ex:
userLiftedFingerOffScreen() {
// do whatever
}
render() =>
<FlatList
data={listData}
renderItem={({item}) => <SomeItem item={item} />}
onScrollEndDrag={this.userLiftedFingerOffScreen}
/>

Related

React Native FlatList: how to reset scroll position

I have a horizontal FlatList in my React Native iOS app. At the bottom of the screen, the FlatList is displayed with 5 items. If you click on the last item (scrolled all the way to the right), the screen will refresh and the FlatList will remain scrolled to the right. How can I get the FlatList to always reset the scroll position (to scroll from the beginning) when the screen changes?
Edit: I have a feeling that my screen is not actually "refreshing" but rather merely changing the data shown on the screen. In this case I may need to trigger a refresh of the screen somehow to cause the FlatList to reset the scroll position? Any help would be greatly appreciated!
const HorizontalScroll = ({items, handlePress}) => {
const renderItem = ({item}) => {
const itemData = { color: item.color, title: item.title };
return <HorizontalItem itemData={itemData} handlePress={handlePress} />;
};
return (
<View style={styles.container}>
<FlatList
data={items}
renderItem={renderItem}
horizontal={true}
numColumns={1}
key={(item) => item.id}
initialNumToRender={5}
scrollToIndex={0}
ItemSeparatorComponent={() => <View style={{width: 8}} />}>
</FlatList>
</View>
);
};
I solved this problem by forcing a refresh of the component. Specifically, I added a key prop to my component so that every time the key value changes, it remounts the component. This causes the FlatList to reset the scroll position to the beginning.
<View key={items[0].title}>
{content}
</View>

How can user change the order of flatlist components by drag and drop

I am using flatlist to show the results of different games, I want my users to change the order as they wish for that I used draggable flatlist function, the problem is i can drag the game but it couldn't place where i want it always comes back to its original position as in API.
Here is the code for flatlist function
<DraggableFlatList
data={this.state.dataSource}
onDragEnd={this.dataSource}
renderItem={this.renderItem}
keyExtractor={item => item.GameId.toString()}
ItemSeparatorComponent={this.renderSeprator}
refreshing = {this.state.refreshing}
onRefresh = {this.handleRefresh}
refreshControl={
<RefreshControl refreshing={this.refreshing} onRefresh={this.getS}
/>
}
/>

How to fix React Native ScrollView default at bottom

I'm trying to build a Chat App with React Native.
In the chat screen, there is a ScrollView for every message sent and received.
However, every time when I leave the screen and back in, the default of the ScrollView is at the top. I've written a function to scrollToEnd after rendered, but that is After Rendered, I need to set the ScrollView at the bottom before it actually shows.
I've tried (but users can see it scrolling as soon as it is rendered):
const scrollToBottom = () => {
scrollEl.current.scrollIntoView({ behavior: 'smooth'})
}
useEffect(() => {
scrollToBottom()
}, [scrollEl, loading, chatHistory])
return(
<ScrollView bottom={0} w="100%" bg="blueGray.100" inverted>
<VStack flex={1} w="100%" id="VStack">
{!loading && chatHistory.chats[roomId] && chatHistory.chats[roomId].length > 0 && chatHistory.chats[roomId].map((chat, index) =>
<ChatItem type={chat.type}
text={chat.text}
fromSelf={chat.fromSelf}
key={index}
/>
)}
<Box ref={scrollEl} />
</VStack>
</ScrollView>
)
I've tried:
Set scale: -1 at CSS, which actually invert the whole screen upside down.
Thus, I have to invert every single message to invert them back to the normal side.
Meanwhile, the scrolling is totally in the opposite way when you scroll by mouse or slide on the screen.
Any solutions or suggestions?
Thanks!
I hardly recommend you to use FlatList cause of the performance of the rendering list
https://reactnative.dev/docs/flatlist
Also in FlatList you can achieve what exactly you want
https://reactnative.dev/docs/flatlist#initialscrollindex
Even if you want better performance below library handle list better than FlatList
https://github.com/Flipkart/recyclerlistview

ScrollView positions being restarted on a child component re-render (update)

I am having an issue (this is probably something I am ignoring within the render cycle) maintaining the position of two different scroll views when some of its child components are updated.
I have a hierarchy of views that is like:
<ScrollView vertical scrolling ability>
...
<ScrollView horizontal and vertical scrolling ability>
...
<Matrix>
<Cell updateCellStatusHandler={handler}>
</Matrix>
...
</ScrollView>
</ScrollView>
So, the updates on the internal cells, are resetting both scrolls on cell status update and this generates a super weird experience with the user having to scroll down/left/right back to continue interacting with the Matrix of cells with a status I have.
I have tried to save the scrollOffset (x,y) using useState but if I change some cell, the state is reseted to (0,0) which is my initial state.
const [scrollOffset, setScrollOffset] = useState({
scrollX: 0,
scrollY: 0,
})
But without luck.
<ScrollView
{...props}
scrollEventThrottle={16}
ref={scrollReference}
// tslint:disable-next-line: jsx-no-lambda
onScroll={event => {
setScrollOffset({
scrollX: event.nativeEvent.contentOffset.x,
scrollY: event.nativeEvent.contentOffset.y,
})
}}
onScrollEndDrag={event => {
console.log(event.nativeEvent.contentOffset.y)
console.log(event.nativeEvent.contentOffset.x)
}}
>
{props.children}
</ScrollView>
One possible approach to solve this is to have a mechanism that allow me to save the scroll position before the update. But this will complicate a lot the communication between components, etc.
By the way, the cell status update is being handled via Redux.
If some of you can bring you some light over this, would be great.
---- UPDATE 1 (Code of the panel component added) ----
Parent component is:
<View style={styles.container}>
<CustomScrollView enableResetScrollToCoords={false}>
<Section>
<WhiteContainer>
<View style={styles.rateContainer}>
<DescriptionTextStatus
statusText={getMessageForRate(availabilityRate)}
descriptionText={getDescriptionForRate(
availabilityRate,
isTeacher,
)}
icon={getImageForRate(availabilityRate)}
/>
</View>
</WhiteContainer>
</Section>
{!loggedUserIsTeacher() && <AvailabilityStart />}
<AvailabilityPanel />
<AvailabilityStatus />
<AvailabilityButtonSave />
</CustomScrollView>
</View>
Availability Panel is one of the childs
export const AvailabilityPanel: React.FunctionComponent<{}> = () => {
const panel: Cell[][] = useSelector((state: ReduxStore) => {
return get(state.entities, 'availability.panel', undefined)
})
if (panel === undefined) {
return <Nothing />
}
return (
<Section>
<WhiteContainer>
<LinearGradient
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
colors={[Palette.White, Palette.White68]}
>
<View style={styles.rateContainer}>
<DescriptionTextStatus
statusText={strings.warning}
descriptionText={strings.selectionMessage}
icon={'clock'}
/>
<View style={styles.separator} />
</View>
<View style={styles.container}>
<ScrollView
style={styles.scrollView}
directionalLockEnabled={false}
horizontal={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
>
<View style={styles.contentContainer}>
<View style={styles.weekdaysContainer}>
<PanelHeader weekdays={weekdays} />
</View>
<View style={styles.rowsContainer}>
{hours.map((hourLabel: string, index: number) => {
return (
<PanelRow
key={index}
hourLabel={hourLabel}
hoursRow={panel[index]}
rowIndex={index}
/>
)
})}
</View>
</View>
</ScrollView>
</View>
</LinearGradient>
</WhiteContainer>
</Section>
)
}
Thanks in advance.
ScrollView does not reset scroll position because of child updates, regardless of whether you use Redux or not. You don't need to try to patch it with workarounds like saving current scroll positions. Rather just figure out what's wrong with your setup
It's hard to tell because you didn't provide your actual code, but I'm pretty sure one or both ScrollViews are remounted on updates (old ScrollView is removed and new one is created in its place with its own scroll position). It's likely that you are declaring new components inside render function so React will just remount them every time
I think the overall idea of saving the scroll position of your scrollview is good.
I think the fact that your state is reset to 0 is probably due to Redux (not sure however, could you precise how your component is connected to redux ?). I am not so familiar with Hooks, but in a React Class component, I would try to save the scroll position in a non state property of the class. Maybe save it in a variable outside of your functional component ?
Then in the handler to manage cell updates, you could ensure that your scrollview scrolls to the position you saved (using ScrollView.scrollTo(), with {animated: false} option so that the animation is not visible for the user)

Append components to a fixed list using react-window?

I have a fixed list and grid using react-window, infinite loader, and auto sizer. It's working fine but I'm looking to append a component before the list/grid starts (for example, a search box and a button). What's the correct approach to accomplish this? I want this component to scroll with the fixed list and not scroll separately. I've tired just rendering the component before but then it's not in the fixed list container and doesn't scroll with it.
{/* WANT TO ADD SOME SORT OF COMPONENT HERE SO IT CAN SCROLL WITH LIST */}
{/* CAN'T IN HERE BECAUSE ITS NOT INSIDE THE FIXED CONTAINER SO IT SCROllS SEPARATELY */}
<AutoSizer>
{({ height, width }) => (
<InfiniteLoader
isItemLoaded={index => index < stateData.data.length}
itemCount={stateData.data.length + 1}
loadMoreItems={loadMoreProducts}
>
{({ onItemsRendered, ref }) => (
<FixedSizeList
onItemsRendered={onItemsRendered}
ref={ref}
height={height}
itemCount={stateData.data.length + 1}
itemSize={350}
width={width}
>
{/* WANT TO ADD SOME SORT OF COMPONENT HERE SO IT CAN SCROLL WITH LIST */}
{/* CAN'T IN HERE BECAUSE ITS LOOPING LISTITEM */}
{ListItem}
</FixedSizeList>
)}
</InfiniteLoader>
)}
</AutoSizer>
Edit: Pretty much I don't want the list (or grid) and the actual page to be two different scroll containers. I'd like the whole page to scroll together. I've come across this issue because some of my containers need to have an infinite list of items users can scroll through so the list needed to be virtualized to improve performance.
See a demo here. Really the fixed container should just be considered the whole page and the search box and everything else should scroll with the infinite list. Having two different scroll containers isn't too great for the ux.
If you want the whole page to scroll with your virtual list you will need a window scroller. This also involves playing around with some styling as well. To make your searchbox scroll with your list you will need a position of fixed. A spacer div and some styling helps the illusion.
I added a basic window scroller and some styling changes to your codepen here.
A few options:
Make a wrapper around The autosizer, that holds both your search box and the autosizer.
<>
<SearchBox />
<AutoSizer>
... your items
</AutoSizer>
</>
let the AutoSizer contain both the search and the list
<AutoSizer>
<SearchBox />
<List>
... your items
</List>
</AutoSizer>
Since your infinite scroll is using a render function you might need fragments
<AutoSizer>
<InfiniteScroll>
{({ onItemsRendered, ref }) => (
<>
<SearchBox />
<List>
... your items
</List>
<>
)}
</InfiniteScroll>
</AutoSizer>
Here I edited your example and made it work using this approach.
Please note that the page scrolls only because of the top and bottom bars; which is the behaviour in your example.
Let the infinite loader populate an array, and append an extra item to that. The item can have a certain type. Assuming the FixedSizeList is the List component from react-virtualized then you can use it's rowRenderer to render the item of type "search" differently than the other items. Something like:
function rowRenderer ({ key, type, value, ...rest }) {
if (type === 'search') {
return <Search key={key} placeholder={value} {...rest} />
}
return <div key={key} {...rest}>{value}</div>
}
// Render your list
ReactDOM.render(
<AutoSizer>
{({ height, width }) => (
<List
height={height}
rowCount={list.length}
rowHeight={20}
rowRenderer={rowRenderer}
width={width}
/>
)}
</AutoSizer>,
document.getElementById('example')
);
Perhaps it also helps to look at the simple example from Vaughn.

Resources