React native infinite scroll with flatlist - react-native

I followed this tutorial https://www.youtube.com/watch?v=rY0braBBlgw
When I scroll down it sends the request then it gets stuck in a loop and just requests and requests. I think this is a problem with the scrollview in the listview.

I am not sure if you were able to resolve this but I was having the same problem and I am adding what worked well for me.
onEndReachedThreshold=>onEndThreshold
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={
<Text style={{textAlign: 'left'}}> {item.name.first} {item.name.last}</Text>
}
subtitle={
<Text style={{textAlign: 'left'}}>{item.email}</Text>
}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
keyExtractor={item => item.email}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndThreshold={0}
/>
I hope this helps someone.

This works for me:
<FlatList
data={this.state.storesList}
renderItem={({ item, index }) => renderItem(item, index)}
keyExtractor={(item, index) => item.id.toString()}
onEndReached={this.fetchMore}
onEndReachedThreshold={0.1}
ListFooterComponent={this.renderFooter}
refreshing={this.state.refreshing}
/>
renderFooter = () => {
if (this.state.refreshing) {
return <ActivityIndicator size="large" />;
} else {
return null;
}
};
fetchMore = () => {
if (this.state.refreshing){
return null;
}
this.setState(
(prevState) => {
return { refreshing: true, pageNum: prevState.pageNum + 1 };
},
() => {
this.sendAPIRequest(null , true);
}
);
};
The reason I used the following in the fetchMore function:
if (this.state.refreshing){
return null;
}
Is because when you setState to the pageNum it calls the render() function and then the fetchMore called again. This is written to prevent it.
In addition, I set:
refreshing: false
after the sendAPIRequest is done.
Pay attention about onEndReachedThreshold in FlatList:
How far from the end (in units of visible length of the list) the
bottom edge of the list must be from the end of the content to trigger
the onEndReached callback.
Meaning in my example (0.1) means: when you reach 10% of items from the bottom, the fetchMore callback is called. In my example, I have 10 items in the list, so when the last item is visible, fetchMore is called.

I'm not sure if this is exactly what you're looking for, but the code I've left below allows you to continue scrolling through a fixed set of data props. When you reach the last index, it basically wraps around to the beginning. I've achieved this by appending a copy of the first element of the supplied data to the end of the FlatList; when the user scrolls this into view, we can safely reset the scroll offset.
import React, { Component } from 'react';
import { FlatList } from 'react-native';
export default class InfiniteFlatList extends Component {
constructor(props) {
super(props);
this.state = {
};
this._flatList = null;
}
getWrappableData = (data) => {
return [...data, data[0]];
}
render = () => (
<FlatList
{ ...this.props }
ref={ (el) => this._flatList = el }
onLayout={ ({nativeEvent}) => {
const {width, height} = nativeEvent.layout;
this.setState({
width, height
});
} }
onScroll={ ({ nativeEvent }) => {
const { x } = nativeEvent.contentOffset;
if(x === (this.props.data.length * this.state.width)) {
this._flatList.scrollToOffset({x: 0, animated: false});
}
} }
data={ this.getWrappableData(this.props.data) }
pagingEnabled={true}
/>
)
};
InfiniteFlatList.defaultProps = { };
InfiniteFlatList.propTypes = { };
This assumes you want to scroll horizontally.
It probably isn't perfect; there is likely a better technique out there which uses FlatList's onEndReached callback, however this only seemed to fire once througohout. By polling the scroll offset of the FlatList, we can fire off our own equivalent as many times as needed. If you specify a getItemLayout prop, you'll be able to use scrollToIndex({index, animated?}) instead.
Aug. 5, 2019 update
On React native 0.60, one should use scrollToOffset as:
this._flatList.scrollToOffset({offset: 0, animated: false});

Related

Edit state of every item of a FlatList

I made a page in which I use a FlatList. This FlatList uses an item component I made that display another view below itself when pressed by setting a state "hidden" to false. The main issue I have is that I can't find a way to change the "hidden" state to true when one of the item is pressed, hence always keeping only 1 item displaying the additional view at the time. In the same time, when I refresh/re-render my FlatList, it does not set all the "hidden" state back to true.
This is where I render my FlatList
_onRefresh() {
this.setState({refreshing: true}, () => this._loadList());
}
render() {
return (
<View style={[style.container, style.whiteBackground]}>
<CategoryFilter filterCallback={this._changeCategory}/>
<FlatList
data={this.state.list}
extraData={this.state}
renderItem={({item}) =>
<ListItemComponent item={item} category={this.state.category}/>
}
refreshing={this.state.refreshing}
onRefresh={() => this._onRefresh()}
/>
</View>
);
}
And this is where I render and display the hidden view
constructor(props) {
super(props);
this.state = {
hidden: true
};
}
componentDidMount() {
this.setState({hidden: true});
}
_onPress() {
this.setState({
hidden: !this.state.hidden
});
}
[...]
_renderOS(item) {
if (Platform.OS === 'android') {
return (
<TouchableNativeFeedback onPress={() => this._onPress()}>
{this._renderItem(item)}
</TouchableNativeFeedback>
);
} else if (Platform.OS === 'ios') {
return(
<TouchableOpacity onPress={() => this._onPress()}>
{this._renderItem(item)}
</TouchableOpacity>
);
}
}
[...]
_renderDescription(item) {
if (this.state.hidden === true) {
return null;
} else {
return (
<View style={listItemStyle.descriptionContainer}>
<Text style={listItemStyle.description}>
{item.description}
</Text>
</View>
);
}
}
I just want to be able to have only one of the list item with hidden set to false at the time and have said item to be set to hidden=true when the page is refreshed, but I never found anything that could help me.
So after thinking a lot I finally found a solution.
Instead of handling the hidden state in every item, I made a list of every hidden state associated to the items ids in the component where my flatlist is, adding a function that will set the previously opened item to hidden and open the new one, and passing it as a callback to my items so that it can be called when I press them.
_onPress(id) {
let items;
items = this.state.items.map((item) => {
if (item.id === this.state.openId)
item.open = false;
else if (item.id === id)
item.open = true;
return item;
});
this.setState({
items: items,
openId: (id === this.state.openId ? '' : id)
});
}
<FlatList
data={this.state.items}
extraData={this.state}
renderItem={({item}) =>
<ListItemComponent
onPress={this._onPress.bind(this)}
bet={item}
categoryList={this.state.categoryList}
open={item.open}/>
}
refreshing={this.state.refreshing}
onRefresh={() => this._onRefresh()}
/>

How to mak FlatList automatic scroll?

Here is what i try i use setInterval function to set a variable content will be changed every second and i find onMomentumScrollEnd can get the position y when scroll the FlatList
And then i am stuck , i thougt event.nativeEvent.contentOffset.y = this.state.content; can let my FlatList automatic scroll. Obviously it is not.
Any one can give me some suggestion ? Thanks in advance.
My data is from an API
Here is my App.js:
import React from 'react';
import { View, Image, FlatList, Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
const equalWidth = (width / 2 );
export default class App extends React.Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.state = { movies: [], content: 0 };
}
componentWillMount() {
fetch('https://obscure-reaches-65656.herokuapp.com/api?city=Taipei&theater=Centuryasia')
.then(response => response.json())
.then(responseData => {
console.log(responseData);
this.setState({ movies: responseData[0].movie });
})
.catch((error) => console.log(error));
this.timer = setInterval(() => {
this.setState({content: this.state.content+1 })
}, 1000);
}
// get the jsonData key is item and set the value name is movie
renderRow({ item: movie }) {
console.log('renderRow => ');
return (
<View>
<Image source={{ uri: movie.photoHref}} style={{ height: 220, width: equalWidth }} resizeMode="cover"/>
</View>
);
}
render() {
const movies = this.state.movies;
// it well be rendered every second from setInterval function setState
console.log('render');
return (
<View style={{ flex: 1 }}>
<FlatList
data={movies}
renderItem={this.renderRow}
horizontal={false}
keyExtractor={(item, index) => index}
numColumns={2}
onMomentumScrollEnd={(event) => {
console.log(event.nativeEvent.contentOffset.y);
event.nativeEvent.contentOffset.y = this.state.content;
}}
/>
</View>
);
}
}
You need to tell your FlatList that you want it to scroll to a new position using scrollToOffset().
Store a reference to your FlatList in your class by adding the prop
ref={flatList => { this.flatList = flatList }} to it.
Then, call this.flatList.scrollToOffset({ offset: yourNewOffset }) to scroll to the desired offset.
Docs on this method are here.

React-Native scroll to top with Flatlist

I'm having a lot of trouble scrolling to the top of my Flatlist so any help would be greatly appreciated!
Essentially it fetches the first 5 items from firebase, then when onEndReached is called we append the next 5 items to the list:
data: [...this.state.data, ...results]
For now I have a refresh button at the top of my view that does the following:
this.flatListRef.scrollToOffset({ animated: true, y: 0 });
If i click this when the first 5 items are rendered it scrolls to the top of the list as expected. The issue only occurs after the list has been appended to (I guess the items are off view?).
I have also tried 'ScrollToItem' however I'm guessing this doesn't work due to the following from React Native docs:
Note: Cannot scroll to locations outside the render window without
specifying the getItemLayout prop.
Can anyone explain what is happening or know what I am doing wrong?
Thank you in advance!
getItemLayout: (not entirely sure what this does or how to work out length & offset etc)
getItemLayout = (data, index) => (
{ length: 50, offset: 50 * index, index }
)
return (
<View>
<FlatList
ref={(ref) => { this.flatListRef = ref; }}
onScroll={this.handleScroll}
data={this.state.data}
keyExtractor={item => item.key}
ListFooterComponent={this.renderFooter()}
onRefresh={this.handleRefresh}
refreshing={this.state.newRefresh}
onEndReached={this.handleEndRefresh}
onEndReachedThreshold={0.05}
getItemLayout={this.getItemLayout}
renderItem={this.renderItem}
/>
{this.state.refreshAvailable ? this.renderRefreshButton() : null}
</View>
);
The correct syntax is
this.flatListRef.scrollToOffset({ animated: true, offset: 0 });
and you can also use
scrollToIndex
Just in case someone is lost on how to do this with hooks, here is an example
function MyComponent() {
const flatListRef = React.useRef()
const toTop = () => {
// use current
flatListRef.current.scrollToOffset({ animated: true, offset: 0 })
}
return (
<FlatList
ref={flatListRef}
data={...}
...
/>
)
}
The main difference is that you access it by .current
FOR REACT HOOKS
import React, {useRef} from 'react'
declare it -> const flatListRef = useRef()
set it like ref={flatListRef}
call it like flatListRef.current.scrollToOffset({animated: false, offset: 0})
In this answer I have mentioned a very easy code snippet where there are 2 buttons to scroll flatlist right or left. You can use this code to achieve other use cases of programmitically scrolling flatlist.
//import
import React, { useEffect, useState, useRef, useCallback } from 'react';
//React class declaration.
const DocumentsInfo = ({ route, navigation }) => {
//state variable
const [documentsArray, setDocumentsArray] = useState({}); // array being shown in flatlist.
const [maxVisibleIndex, setMaxVisibleIndex] = useState(0); // highest visible index currently visible.
const [minVisibleIndex, setMinVisibleIndex] = useState(0); // lowest visible index currently visible.
const flatListRef = useRef() // reference of flatlist.
// callback for whenever flatlist scrolls
const _onViewableItemsChanged = useCallback(({ viewableItems, changed }) => {
setMaxVisibleIndex(viewableItems[viewableItems.length - 1].index);
setMinVisibleIndex(viewableItems[0].index);
}, []);
// function for scrolling to top
const scrollToTop = () => {
setMinVisibleIndex(0);
setMaxVisibleIndex(0);
flatListRef.current.scrollToIndex({ index: 0, animated: true });
};
// function for scrolling to bottom
const scrollToBottom = () => {
let temp = documentsArray.length - 1;
setMinVisibleIndex(temp);
setMaxVisibleIndex(temp);
flatListRef.current.scrollToIndex({ index: temp, animated: true });
};
// function for moving flatlist left and right by 1 index
const moveNextPreviousHorizontalFlatlist = (isNext) => {
if (isNext) {
let maxVisible = maxVisibleIndex + 1;
if (maxVisible < documentsArray.length) {
let minVisible = minVisibleIndex + 1;
setMinVisibleIndex(minVisible);
setMaxVisibleIndex(maxVisible);
flatListRef.current.scrollToIndex({ index: maxVisible, animated: true });
}
}
else {
let minVisible = minVisibleIndex - 1;
if (minVisible >= 0) {
let maxVisible = maxVisibleIndex - 1;
setMinVisibleIndex(minVisible);
setMaxVisibleIndex(maxVisible);
flatListRef.current.scrollToIndex({ index: minVisible, animated: true });
}
}
};
// UI
return (
<View>
{ maxVisibleIndex != documentsArray.length - 1 &&
<View style={styles.Refresh}>
<TouchableOpacity onPress={() =>
moveNextPreviousHorizontalFlatlist(true)
}>
<Image style={styles.Refresh} source={Refresh} />
</TouchableOpacity>
</View>
}
<FlatList
ref={flatListRef}
onViewableItemsChanged={_onViewableItemsChanged}
showsHorizontalScrollIndicator={false}
horizontal
keyExtractor={(item, index) => item.fileName + index}
data={documentsArray}
renderItem={({ item, index }) => {
return ( <DocumentListItem /> )
}}
/>
{ minVisibleIndex != 0 &&
<View style={styles.Refresh}>
<TouchableOpacity onPress={() =>
moveNextPreviousHorizontalFlatlist(false)
}>
<Image style={styles.Refresh} source={Refresh} />
</TouchableOpacity>
</View>
}
</View>
);
Below method solved my problem. Check it out:
const flatList = useRef();
const moveToTop = () => flatList.current.scrollToIndex({ index: 0 });
return (
<View>
<FlatList
ref={flatList}
onScroll={this.handleScroll}
data={this.state.data}
keyExtractor={item => item.key}
ListFooterComponent={this.renderFooter()}
onRefresh={this.handleRefresh}
refreshing={this.state.newRefresh}
onEndReached={this.handleEndRefresh}
onEndReachedThreshold={0.05}
getItemLayout={this.getItemLayout}
renderItem={this.renderItem}
/>
{this.state.refreshAvailable ? this.renderRefreshButton() : null}
</View>
);
How to add scroll to top to a FlatList in ReactNative app

Implement Bidirectional Infinite ScrellView in react-native

I want to implement infinite scrollview in both the direction. Also the data should be loaded dynamically.
I am using SectionList component for list. I have implemented forward infinite scrolling. That means if user scroll down, the data will append to list automatically.
For that I have used onMomentumScrollEnd event. When user stops the scrolling, if the scroll is in Up direction, data will be appended at the End and if the scroll is in Down direction, data will be appended at the Top.
Now the problem is when I append the data at Top of list, It shift all the current list data to backward. I don't want to shift the current list even if the data is updated. Is there any way to do it.
This is my code:
import React, {Component} from 'react';
import {
Text,
View,
StyleSheet,
SectionList,
} from 'react-native';
import CardComponent from './CardComponent'
export default class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
sectionData: [],
loading: false,
}
this.contentOffsetY = 0;
this._onScroll = this._onScroll.bind(this)
}
componentDidMount() {
this.setState({ sectionData: this.props.data })
}
renderItem = ({item}) => (
<CardComponent
data={item}
key={item}
/>
);
renderDateSeparator(text) {
return (
<Text style={{
paddingVertical: 15,
fontSize: 14,
flex: 1,
textAlign: 'center',
textAlignVertical: 'center',
}}>
{text}
<Text>
)
}
_onScroll(e){
let contentOffset = e.nativeEvent.contentOffset.y;
this.contentOffsetY < contentOffset ? this.loadMoreOnBottom() : this.loadMoreOnTop();
this.contentOffsetY = contentOffset;
}
loadMoreOnTop() {
this.setState({ lodaing: true });
// code to append data on top of list
this.setState({ lodaing: false });
}
loadMoreOnBottom() {
// code to append data at bottom of list
}
render() {
const sectionData = this.state.sectionData;
return(
<View style={{flex: 1}}>
<SectionList
onMomentumScrollEnd={this._onScroll}
automaticallyAdjustContentInsets={false}
itemShouldUpdate={false}
renderItem={this.renderItem}
renderSectionHeader={({section}) => this.renderDateSeparator(section.date)}
sections={sectionData}
stickySectionHeadersEnabled={false}
refreshing={this.state.loading}
onRefresh={() => this.loadMoreOnTop()}
onEndReachedThreshold={0.3}
onEndReached={() => this.loadMoreOnBottom()}
keyExtractor={(item) => item.key}
/>
</View>
)
}
}
Thanks in advance.
After so much of research, I have finally implemented the bidirectional infinite scroll view in react-native.
For the implementation, I have replaced my SectionList with FlatList, Because I want to use scrollToOffset method which is not properly working in SectionList.
I have used setInterval function of javaScript. It regularly checks weather the list need to be append from top or bottom.
This is my code:
import React, {Component} from 'react';
import {
Text,
View,
StyleSheet,
FlatList,
Dimensions,
} from 'react-native';
import CardComponent from './CardComponent'
let {height, width} = Dimensions.get('window');
export default class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
listData: [],
}
this.contentOffsetY = 0;
this.pageOffsetY = 0;
this.contentHeight = 0;
this._onScroll = this._onScroll.bind(this);
this.loadMoreOnTop = this.loadMoreOnTop.bind(this);
this.loadMoreOnBottom = this.loadMoreOnBottom.bind(this);
}
componentDidMount() {
this.setState({ listData: this.props.data });
this._interval = setInterval(() => {
this.setState({ load: true });
}, 2000);
}
componentWillUnmount() {
clearInterval(this._interval);
}
renderItem = ({item}) => (
<CardComponent
data={item}
key={item}
/>
);
_onScroll(e){
let contentOffset = e.nativeEvent.contentOffset.y;
this.contentOffsetY < contentOffset ? this.loadMoreOnBottom() : this.loadMoreOnTop();
this.contentOffsetY = contentOffset;
}
scrollToOffset = (offset) => {
this.flatListRef ? this.flatListRef.scrollToOffset({animated: false, offset}) : null;
};
loadMoreOnTop() {
let newOffset;
// code to append data on top of list
// calculate newOffset:
newOffset = this.pageOffsetY + space required for new data.
this.contentOffsetY = newOffset;
this.scrollToOffset(newOffset);
}
loadMoreOnBottom() {
// code to append data at bottom of list
}
render() {
const listData = this.state.listData;
if(this.pageOffsetY < 600) {
this.loadMoreOnTop();
} else if((this.contentHeight - this.pageOffsetY) < (height * 1.5)){
this.loadMoreOnBottom();
}
return(
<View style={{flex: 1}}>
<FlatList
onScroll={(e) => {
this.pageOffsetY = e.nativeEvent.contentOffset.y;
this.contentHeight = e.nativeEvent.contentSize.height;
return null;
}}
onMomentumScrollEnd={this._onScroll}
automaticallyAdjustContentInsets={false}
itemShouldUpdate={false}
renderItem={this.renderItem}
data={listData}
refreshing={false}
onRefresh={() => this.loadMoreOnTop()}
onEndReachedThreshold={0.3}
onEndReached={() => this.loadMoreOnBottom()}
keyExtractor={(item) => item.key}
ref={(ref) => { this.flatListRef = ref; }}
animated={false}
/>
</View>
)
}
}

Performance issue of flatList in react native

I've tried flatlist but it has a bit of performance issues in android.
As I scroll down, it loads the list. But afterwards, it shows blank while scrolling upwards.
After reaching the end of the screen, it stops for a while and then loads the datas. Why is it not showing loader (activity indicator) at the bottom? Why is onEndReached and onEndReachedThreshold not working?
Plz have a look at the video here
https://youtu.be/5tkkEAUEAHM
My code:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
} from 'react-native';
import { List, ListItem, SearchBar } from "react-native-elements";
export default class FlatListExample extends Component
{
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false,
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
console.log('url', url);
this.setState({ loading: true });
setTimeout(()=>{
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
},0);
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
handleLoadMore = () =>{
this.setState({
page:this.state.page + 1,
},()=>{
this.makeRemoteRequest();
})
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
/>
)}
keyExtractor={item => item.email}
ListFooterComponent={this.renderFooter}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
);
}
}
AppRegistry.registerComponent('FlatListExample', () => FlatListExample);
I've noticed that you're not setting initialNumToRender. From the docs:
initialNumToRender: number
How many items to render in the initial batch. This should be enough
to fill the screen but not much more. Note these items will never be
unmounted as part of the windowed rendering in order to improve
perceived performance of scroll-to-top actions.
So you'll want to estimate how many cells you expect to be visible at any given time and set it to that. I'd also recommend if you haven't already to update to the latest react-native which includes various improvements on the FlatList component.