React Native FlatList: how to reset scroll position - reactjs

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>

Related

FlatList rendering is heavy for big data set

In my application I have a FlatList with dataset of 100 items in it. Each item has a complex UI and I noticed that it's taking a heavy toll on performance. The page that has the list takes up to 5 seconds to load.
I noticed that the moment the component is rendered for the first time, the renderItem function of the FlatList is also called for each and every item in my data set, I also noticed that it also happens if there any other setState change for other stuff on that page. Is there a way to prevent this re-rendering of the flat list or at least to re-render only the visible items - just like with Recycle with in native Android?
How can I only render the visible items when the component first appears?
I tried to use initialNumToRender and maxToRenderPerBatch but neither have worked.
Here is an example of the code:
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const [text, setText] = React.useState('')
const renderItem = ({ item }) => {
console.log("Render Item")
return (<Item title={item.title} />)
};
return (
<SafeAreaView style={styles.container}>
<TextInput
value={text}
onChangeText={(val) => setText(val)}
/>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
initialNumToRender={6}
maxToRenderPerBatch={6}
/>
</SafeAreaView>
);
}
If I try to type something in TextInput the whole list re-renders but nothing in the list has changed.. how can I prevent this?

React Native FlatList returned from a function or const rerenders from the top when an item is interacted with

I'm trying to put a flatlist checklist into a react native tab view, to do that you need to declare the flatlist as a const so it can be used like . Unfortunately whenever I click on an item at the bottom of the list it pops back to the top and rerenders the list. This problem doesn't occur when a flatlist is rendered directly into your components render function.
I've made a simplified version of what I mean.
https://snack.expo.io/ecO7YYlVZ Is the version where clicking an item pops to the top
https://snack.expo.io/1iQ!ILk4B Is the version where clicking an item doesn't pop to the top.
The only difference between the two is the not working version is like this
const Dat = () => {
return (
<FlatList
style={styles.container}
data={rowsData}
renderItem={this.renderItem}
keyExtractor={extractKey}
/>
);
};
return <Dat />;
Whereas the working version is like this
return (
<FlatList
style={styles.container}
data={rowsData}
renderItem={this.renderItem}
keyExtractor={extractKey}
/>
);
EDIT: I need to have the flatlist in a way that I can add it to a tab using react-native-tab-view: https://github.com/react-native-community/react-native-tab-view
Dat component must be outside of render()
because it reinitializes the whole component and then rerenders that
which cause that behavior
instead, when you create a component outside of the render(), the component itself does not reinitialize only data updated
Your Dat component
const Dat = () => {
return (
<FlatList
style={styles.container}
data={rowsData}
renderItem={this.renderItem}
keyExtractor={extractKey}
/>
);
};
Check the example with the outside of render()
https://snack.expo.io/#jsfit/flatlist

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)

Navigate to same screen with different parameters using - ReactNavigation : React Native

I have been trying to navigate to the same screen with different parameters on react-native application. It is a category screen, where I don't want the user to go back and select different categories to view products. I have made a screen where products from categories are displayed.
Below is my code:
<View style={styles.horizontalSlider}>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={this.state.DataSource}
keyExtractor={(item, index) => index.toString()}
extraData={this.state.selectedItem}
renderItem= { ({item}) => (
<TouchableOpacity onPress={(category) => navigate.push('CategoryScreen', {item})}>
<ImageBackground style={styles.profileImgContainer} >
<Image source={{ uri: item.images }} style={[styles.profileImg, , { borderColor: 'green', borderWidth:2 }]} />
</ImageBackground>
</TouchableOpacity>
)}
/>
</View>
If you want to navigate to the same screen with different parameters and be unable to go back, use this.props.navigation.replace('CategoryScreen', { ...new params here... })
If you want to change current navigation params without navigating, use this.props.navigation.setParams()
(navigation prop)
But your problem is not a navigation problem. You don't need multiple screens for that. Solve it with react. All you need to do is to render your list conditionally depending on what category (or circle item?) is currently selected. Store this information in state and use it in render
You can add unique key:
this.props.navigation.navigate({ routeName: route, params: params, key: route + params.id })

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

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}
/>

Resources