I try to reload my feed in my flatlist with refreshControl. The scroll works fine, so if I scroll down the ActivityIndicator shows. But the problem is that the flatlist doesn't updates. Can any one see why or have a another solution to reload a flatlist?
This is my reload function:
return new Promise(resolve => setTimeout(resolve, timeout));
}
const onRefresh = React.useCallback(() => {
setRefreshing(true);
props.reload()
wait(2000).then(() => setRefreshing(false));
}, []);
This is how I display it in my flatlist
<FlatList
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
/>
}
numColumns={1}
horizontal={false}
data={filteredDataSource}
extraData={filteredDataSource}
renderItem={({ item }) =>
{My Items here}
/>
}
in my Action.js (redux) I have I Function called reload()
export function reload() {
return ((dispatch) => {
dispatch(clearData())
dispatch(fetchUsersData())
dispatch(fetchUser())
dispatch(fetchUserPosts())
dispatch(fetchFollowingUsersPosts())
})
}
Here I set my FilteredDataSource to my posts
useEffect(() => {
let posts = [];
if (props.usersPostLoaded == props.allPosts.length) {
for (let i = 0; i < props.allPosts.length; i++) {
const user = props.users.find(el => el.uid === props.allPosts[i]);
if (user != undefined) {
posts = [...posts, ...user.posts]
}
}
posts.sort(function (x, y) {
return x.creation - y.creation
})
setPosts(posts);
setFilteredDataSource(posts);
setMasterDataSource(posts);
}
}, [props.usersPostLoaded]);
You can use the extradata prop in flatlist and your flatlist will refresh with change in that data.
extraData={userData}
https://reactnative.dev/docs/flatlist#extradata
One of the way to Implementation it , usage from useEffect for control your life cycle and in this case controlling refreshing data , you can add this code:
React.useEffect(()=>{
if (refreshing){
dispatch(clearData())
dispatch(fetchUsersData())
dispatch(fetchUser())
dispatch(fetchUserPosts())
dispatch(
fetchFollowingUsersPosts()
)
setRefreshing(false);
};
} , [refreshing])
Also change your onRefresh to:
const onRefresh = React.useCallback(() => {
setRefreshing(true);
}, []);
You can use the extraData prop in Flatlist.
According to the FlatList docs, re-renders can be caused by passing extraData={this.state} to FlatList.
<FlatList
extraData={this.state.index}
showsVerticalScrollIndicator={false}
removeClippedSubviews={false}
data={this.props.response}
keyExtractor={(item, index) => index}
renderItem={({item, index}) => this.renderFlatListItem (item, index)}
/>
Now update it like this
this.setState(prevState => ({index: prevState.index + 1}));
Related
We have a cardList react native component that is a child of search component.
export default function CardList(props) {
keyExtractor = (item, index) => index.toString()
renderItem = ({ item }) => (
<ListItem
title={item.name}
subtitle={item.subtitle}
leftAvatar={{
source: item.avatar_url && { uri: item.avatar_url },
title: item.name[0]
}}
bottomDivider
chevron
/>
)
return (
<FlatList
keyExtractor={keyExtractor}
data={props.images}
renderItem={renderItem}
/>
);
}
The Search fetches data async from backend which takes a couple of seconds and is done with useEffect, for some reason the setKeys in useEffect does not re-render the cardList component. When I refresh artificially with hot-reload on expo the cardList renders fine. Why does setKeys (useState) not render the component?
Thanks for any help!
const [keys, setKeys] = useState([]);
useEffect(() => {
const fetchData = async () => {
const imgkeys = await << 5 second long backend call >>;
setKeys(imgkeys);
}
fetchData();
}, []);
return (
<View>
<View style={{
padding: 5,
}}>
{ (keys) && (keys.length>0) && <CardList images={keys}/> }
</View>
</View>
);
setState is asynchronous for performance reasons and shouldn't be forced to be synchronous just because state updates weren't performed correctly.
You can simply define the useState like that:
const [, forceUpdate] = React.useState(0);
forceUpdate(n => !n)
function MyComponent() {
const flatListRef = React.useRef()
const toTop = () => {
// use current
flatListRef.current.scrollToOffset({ animated: true, offset: 0 })
}
return (
<FlatList
ref={flatListRef}
data={...}
...
/>
)
}
Try this:
onScrollToTop function
You can use onRefresh function like billow
<FlatList
ref={flatListRef}
data={...}
onRefresh={yourFunction}
/>
get more about flatlist read the doc
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
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});
I am trying to manipulate a component under renderItem in a FlatList when onViewableItemsChanged is called.
My code looks like this:
<FlatList
data={this.props.data.allPosts.nodes}
ListHeaderComponent={() => this.props.listHeader}
onViewableItemsChange={this.onViewableItemsChanged}
renderItem={({item}) =>
<View style={{ marginBottom: 12 }}>
<Video lights={true}
ref={(ref) => this[`postRef_${item.key}`] = ref}
/>
</View>
}
/>
onViewableItemsChanged = ({viewableItems}) => {
viewableItems.forEach((item) => {
const { isViewable, key } = item;
if(isViewable) {
const ref = this[`swiperRef_${key}`];
if(!ref) return console.log('Ref not found');
console.log('ref', ref)
ref.paused = false
}
});
}
My issue is that the reference keeps returning undefined. Any way around it?
I am not sure what you mean by "the reference" but if you are referring to const ref = this[swiperRef_${key}];
Have you bound this to onViewableItemsChanged via
this.onViewableItemsChanged = this.onViewableItemsChanged.bind(this)
or
onViewableItemsChange={this.onViewableItemsChanged.bind(this} ?