Flatlist onEndReached endless loop - reactjs

I am using state to store the following data.
state = {
refresh: true,
isFetching: true,
showLoadingBottom: false,
data: []
};
on componentDidMount I manually call a function _fetchData which loads data into this.state.data.
When the flatlist is scrolled to the end it fires _fetchData twice which ends up returning the same data twice ( which is another problem, why does it fire twice? ).
Once the flatlist reaches the end ie, no more data is returned from the server, it goes into an endless loop since the onEndReached continuously fires over and over again even though no new data is returned from the server and this.state.data remains the same.
This is my render code
render() {
return (
<View
style={{
flex: 1
}}>
<FlatList
refreshControl={
<RefreshControl
refreshing={this.state.refresh}
onRefresh={() => {
this.setState({
refresh: true
}, this._fetchData);
}}
title={"Pull To Refresh"}
tintColor={darkGrey}
titleColor={darkGrey}/>
}
onEndReachedThreshold={0.5}
onEndReached={() => {
this.setState({
showLoadingBottom: true
}, () => {
this._fetchData();
});
}}
showsVerticalScrollIndicator={false}
data={this.state.data}
ListFooterComponent={() => {
return (
this.state.showLoadingBottom &&
<View style={{padding: 10}}>
<ActivityIndicator size="small" color={colorAccent}/>
</View>
);
}}
renderItem={this._renderItem}
keyExtractor={(item) => item.id.toString()}
removeClippedSubviews={true}
/>
</View>
);
}

Here is my solution that can maybe be changed to suit other peoples needs:
Basically the important parts are onEndReached={this.state.moreData && this.retrieveMore}. So you can test inside your onEndReached function weather there is anymore data (In my case if we only return 1 object i know it's finished) then set state this.state.moreData to false.
<SafeAreaView style={styles.container}>
<FlatList
data={Object.values(this.state.documentData)}
// Render Items
renderItem={({ item }) => (
<ItemSelector
item={item}
onPress={() => {this.selectItem(item)}}
/>
)}
// On End Reached (Takes in a function)
onEndReached={this.state.moreData && this.retrieveMore}
// How Close To The End Of List Until Next Data Request Is Made
onEndReachedThreshold={1}
ListEmptyComponent={
<Text>No jobs to show</Text>
}
/>
</SafeAreaView>
retrieveMore = async () => {
try {
// Set State: Refreshing
this._isMounted && this.setState({ refreshing: true });
fbDb.ref('job')
.orderByKey()
.startAt(this.state.lastVisible) //Start at the last item we found
.limitToFirst(this.state.limit) //Limit queries returned per page
.once('value', snapshot => {
//check if we got a result
if(snapshot.numChildren() > 1){
.....
this._isMounted && this.setState({
documentData: newstate,
lastVisible: lastVisible,
refreshing: false, //Hide loading icon
});
} else {
this._isMounted && this.setState({
refreshing: false, //Hide loading icon
moreData: false
});
}
});
}
catch (error) {
console.log(error);
}
};

I have a similar problem. In my case it is because the ListFooterComponent.
If you render the ListFooterComponent with this pattern or equivalent
onEndReachedThreshold={x} // for any x >= 0
ListFooterComponent={() => {
if (isDataFetching) {
return <SomeComponentWithSomeHeight />
} else {
return undefined;
}
}}
It will trigger onEndReached infinitely when the user scrolls down the end of the list (or if your content is not longer than the list's visibility area).
And it is because the presence and absence of the <SomeComponentWithSomeHeight /> affects the height of the content and thus triggers the endReached re-calculation.
And following are the possible solution I can think of.
Use negative onEndReachedThreshold that is always "higher" than the height of the ListFooterComponent. But I don't like this solution because it is difficult to know the "higher" (it is relative to the FlatList's visibility area). And the negative onEndReachedThreshold may cause some issue on Android.
Implement your own loading overlay outside of the FlatList so that the loading component does not affect the content height.
Set opacity = 0 to hide the ListFooterComponent instead of returning undefined, so that it is always there and the content height does not change when it becomes visible.
ListFooterComponent={() => {
return (
<View style={{ opacity: isDataFetching ? 1 : 0 }}>
<SomeComponentWithSomeHeight />
</View>
);
}}

You are using a component that is being rendered when you are loading data, right? So your flatlist needs to rerender. You meed to make sure you only call you fetch method once even thou your end is reached multiple times.

Related

setState() block the UI when two setState() run

Here is my componentDidMount() method :
componentDidMount() {
const subscription = accelerometer.subscribe(({ x, y, z, timestamp }) => {
x = Math.trunc(x*100);
this.setState({x})
});
}
In above method, every 100 millisecond state is changing. I used that state in my render() method as below :
render() {
const animatedImageStyle = StyleSheet.flatten([
styles.captureButton,
{
transform: [{rotateZ:this.state.x + 'deg'}]
}
])
return (
<SideMenu
menu={leftMenu}
isOpen={this.state.isOpenLeftMenu}
menuPosition={'left'}
bounceBackOnOverdraw={false}
onChange={(isOpenLeftMenu) => this.updateLeftMenuState(isOpenLeftMenu)}
>
<View>
<TouchableOpacity
activeOpacity={0.5}
onPress={(this.state.recordingMode == 'camera')?() => this.takePicture():() => this.toggleRecording()}
>
<Image
source={require('../assets/imgs/forRotate.png')}
style={animatedImageStyle}
/>
</TouchableOpacity>
</View>
</SideMenu>
)
}
Now, the problem is that when I trying to open sidemenu, it is not opening, I mean it opening but hanging too much. My whole app hanging too much.
I think that's because of below method :
updateLeftMenuState(isMenuOpen) {
this.setState({
isOpenLeftMenu:isMenuOpen
})
}
Notice that I am updating another state called isOpenLeftMenu, which may blocked during I update state x.
Can anyone tell me what't going wrong here ?
you can move the animation view in a separate component along with subscription logic. So the state update of that component won't affect the SideMenu component.

Only show redo search button when user has completely moved to a new area

How can i only show the redo button only when the user has changed region?In my code right now it keeps flickering on and off as the region changes, not sure what is missing.
Code:
app.js
onregionchange() {
this.setState({redosearch: !this.state.redosearch })
}
render() {
const Showredo = ({redosearch }) => redosearch ? <View><Text> redo now <Text></View> : null
return(
<View>
{this.state.redosearch ? <ShowRedo redosearch={this.state.redosearch }/> : null}
<View>
<MapView
ref={(map) => (this.map = map)}
style={styles.map}
onRegionChange={this.onregionchange}
>
</MapView>
)
}
There are a few things I notice may be causing an issue here. First, it looks like you may be double checking the state value; inside the Showredo element property and again inside the return value. second, The code has two open View tags and two Text tags with no close. Third, I can't see if the function onregionchange is bound or not. And finally, you're returning two elements in the render function (or actually missing two view closing tags at the end)
try to change your code to this which should correct all of those:
onregionchange = () => {
this.setState({redosearch: !this.state.redosearch })
}
render() {
const { redosearch } = this.state;
return([
<View key="a_key_for_element_1">
{redosearch ? <View><Text> redo now </Text></View> : null}
</View>,
<MapView
key="a_key_for_element_2"
ref={(map) => (this.map = map)}
style={styles.map}
onRegionChange={this.onregionchange}
>
</MapView>]
);
}

Crash when doing scrollToLocation on SectionList

We have an edge case in our app. After the UI is rendered and the user tries to scroll to a section it throws scrolltoindex should be used in conjunction with getitemlayout or on scrolltoindex failed. Now this happens only when he does this immediately after UI render.
_scrollToSection = index => {
setTimeout(() => {
this.list.scrollToLocation({
animated: true,
itemIndex: -1,
sectionIndex: index,
viewPosition: 0
});
}, 150);
};
Section list render:
<SectionList
sections={this.props.sections}
extraData={this.props.subscriber}
ref={ref => {
if (ref) {
this.list = ref;
}
}}
automaticallyAdjustContentInsets={true}
contentInsetAdjustmentBehavior={'automatic'}
windowSize={100}
ListHeaderComponent={this.props.header || null}
ItemSeparatorComponent={() => (
<Separator
style={[mediumStyle.separatorEnd, { backgroundColor: IOS_GREY_02_03 }]}
/>
)}
renderSectionFooter={() => <View style={{ height: 17 }} />}
keyExtractor={(item, index) => index}
removeClippedSubviews={false}
stickySectionHeadersEnabled={true}
renderSectionHeader={({ section }) => (
<SectionTitle title={section.title} theme={this.props.theme} />
)}
renderItem={this._renderItem}
onEndReachedThreshold={0}
onEndReached={() => HapticFeedback.trigger()}
scrollEventThrottle={16}
/>
I tried to google the cause but was unsuccessful finding only outdated and closed issues without a solution. Did this happen to anybody else? How did you fixed it?
UPDATE:
We have come up on a solution of constant item sizes which also takes in count the accessibility scale factor. So we had an item and header size we could use in getItemLayout. All worked as should, but the SectionList is glitchy. When we scrolled to lower sections, the list was jumpy by itself without any interaction.
So far the best solution we had was to build the section list ourselves in native code and use that instead of the RN list.
You're getting this error because scrollToIndex has failed and you have not implemented getItemLayout or onScrollToIndexFailed
getItemLayout in a section list is not exactly fun to setup however this medium post goes over how to do it https://medium.com/#jsoendermann/sectionlist-and-getitemlayout-2293b0b916fb
They suggest react-native-section-list-get-item-layout to calculate the sizes of the layout https://github.com/jsoendermann/rn-section-list-get-item-layout
onScrollToIndexFailed is easier to setup you can add the prop onScrollToIndexFailed={(info) => { /* handle error here /*/ }} You can catch the error and then decide on how you will handle it here.
I would also add a check to make sure that your reference to this.list exists before calling the scrollToLocation function. Something like this.
_scrollToSection = index => {
setTimeout(() => {
if (this.list) {
this.list.scrollToLocation({
animated: true,
itemIndex: -1,
sectionIndex: index,
viewPosition: 0
});
}
}, 150);
};

ReactJS - Dynamic State

How do I dynamically create the state so I can assign 'open'/'close' based on front-end requirements?
E.g.
I have a table with rows in which I have a Fab component. That component has to set a unique ID to open/close to handle the animation. I do not know how many of these rows I will have so I can't pre-populate the state with active1, active2 etc.
Code Example (In this example I am passing 'active1', 'active2' with the object manually, but realistically that isn't a real approach I feel.
{Object.keys(characters).map((k, i) => {
<Fab
active= // how do I specify the state of 'this' instance?
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => this.setState({ //how so I specify 'this' instances state? })}>
...redacted
I've tried to add a sample similar to my code. Let's say I am passing in 5 Users and each User should have this Fab. At this point my state is blank so I have tried implementing a for each loop into componentdidmount to populate the state, this worked, but I had two issues based on how I managed state.
A) All fabs were open, always.
B) No fabs would ever open.
This is because of my onPress not updating the correct state.
I feel like I have to be way over-complicating this issue and I am tearing up my code to get it to work, so I figured I'd come here to ask.
Let me know what other code I can share.
A way of achieving this is to keep an activeArray in state,
which you can toggle activeness of an element buy pushing or removing the element (or just its id or some property which is unique) from activeArray.
So there should be a function on onPress like:
handlePress = (k) => {
if(this.state.activeArray.indexOf(k) > -1){
// remove element
// for example:
this.setState(state => ({activeArray: state.activeArray.filter(activeElement => activeElement !== k)}))
} else {
// push element
// for example:
this.setState(state => ({activeArray: [...state.activeArray, k]}))
}
}
Then for Fab component:
<Fab
active={this.state.activeArray.indexOf(k) > -1}
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => this.handlePress(k)}>
This line:
active={this.state.activeArray.indexOf(k) > -1}
means if k is inside activeArray then active is true.
You can set the dynamic state using JSON bracket ([]) notation.
{
Object.keys(characters).map((k, i) => {
let dynamicStateName = "active"+i //it will set the dynamicStateName as active with index. ie, active1, active2 and so on
return(
{ this.state[dynamicStateName] !== false || this.state[dynamicStateName] !== undefined ?
(<Fab
active= {dynamicStateName}// specify the state of 'this' instance here as dynamicStateName
direction="left"
containerStyle={{}}
style={{
backgroundColor: "#5067FF"
}}
position="topRight"
onPress={() => {(this.state[dynamicStateName] != undefined) ? (this.state[dynamicStateName] ? this.setState({ [dynamicStateName]: false }) : this.setState({ [dynamicStateName]: true })) : this.setState({ [dynamicStateName]: true })} } /> // it will set the state as true if undefined. It will act as toggleable
):
( <button onPress={() => this.setState({ [dynamicStateName]: true })}>OpenFabButton<button>
// initially the dynamicState will be either undefined or false. At that time the button will be show. On clicking the button it will enable the fab component
)
)
})
}
This will fix yours

Flatlist scroll references with getItemLayoutProp

I've got a little problem with flatlist and scroll methods.
I have flatlist with comments and if new one is added I want to scroll list to the bottom to see added comment.
Using scrollToIndex doesn't work properly, native keep showing errors due to lack of getItemLayout, and here is another problem with setting this function - every item can have different size.
scrollToEnd has some issues, sometimes it scrolls to almost bottom of the page, sometimes to headerComponent elements which are set in flatlist props.
Do you have any ideas how to make it scroll to the bottom?
To use scrollToIndex you need to use getItemLayout. There is no point in using it if you have no intention to use getItemLayout. Here is an example taken from the react-native docs:
class ScrollToExample extends Component {
getItemLayout = (data, index) => (
{ length: 50, offset: 50 * index, index }
)
scrollToIndex = () => {
let randomIndex = Math.floor(Math.random(Date.now()) *
this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index:
randomIndex});
}
scrollToItem = () => {
let randomIndex = Math.floor(Math.random(Date.now()) *
this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: "" +
randomIndex});
}
render() {
return (
<FlatList
style={{ flex: 1 }}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={item => item}
getItemLayout={this.getItemLayout}
initialScrollIndex={50}
initialNumToRender={2}
renderItem={({ item, index}) => (
<View style={{...style, backgroundColor: this.getColor(index)}}>
<Text>{item}</Text>
</View>
)}
{...this.props}
/>
);
}
}
https://gist.github.com/joshyhargreaves/b8eb67d24ce58a6d8bffb469f7eeaf39
Hope this helps!

Resources