Related
I am trying to rendering the value from API, i can see array if i console the values, but not able to see it from Flatlist.
API fetch line is here
i think this is where i am making mistakes
console output array
Help me please,
Data returned from the backend is an array of objects. Refactor renderItem props as below.
<Flatlist
keyExtractor={(item) => item.id}
data={data}
renderItem={({ item }) => (
<>
<Text>{item.date}</Text>
<Text>{item.date_gmt}</Text>
</>
)}
/>
Well, you are doing Two things wrong here. Let's start with the first one which is the loading part.
NOTE: I WILL BE USING YOUR EXISTING FLATLIST PROPS AS ...otherProps.
Your are conditional rendering the whole Flatlist component, instead you can use ListEmptyComponent of flatlist to render loading or empty list views.
<Flatlist
...otherProps
ListEmptyComponent={() => {
if(isLoading){
return <ActivityIndicator/>
}
}
/>
Flatlist is a Pure Component, which means it needs a way to know if it should re-render, luckily we have an extraData prop which is for exactly this purpose. Taking from the previous example.
<Flatlist
...otherProps
extraData={data}
ListEmptyComponent={() => {
if(isLoading){
return <ActivityIndicator/>
}
}
/>
PS: you should really checkout the documentation for more information here
https://reactnative.dev/docs/flatlist#listemptycomponent
Basically, I have a list of items (with images) inside a single Card (custom component). Because the rendering of those items is slow, I wanted to use a FlatList to render them incrementally.
Unfortunately, I get the expected error
VirtualizedLists should never be nested inside plain ScrollViews ...
But I don't actually want to use a ScrollView inside the Card. I just want to render a few Items in a single Card, which should change its size to fit all the items.
Setting scrollEnabled={false} on the FlatList still shows the error above.
Using the ListHeaderComponent and ListFooterComponent props is not an option, because the content above and below should NOT be rendered inside the Card.
Here is a minimal example of what I mean:
const Comp = () => {
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<Text>Header</Text>
<Card>
<FlatList
data={data}
renderItem={({ item }) => (
<Image source={{uri: item.localImageUrl}}/>
)}
keyExtractor={(item) => item.id}
scrollEnabled={false}
initialNumToRender={0}
maxToRenderPerBatch={3}
contentInsetAdjustmentBehavior='automatic'
/>
</Card>
<Text>Footer</Text>
</ScrollView>
);
};
What's interesting though, that aside from the error - I get the result I expected, and I could technically hide that error and ignore it, but that does not seem like the recommended approach.
Important: I am not specifically looking for a FlatList solution. It could technically be any Component that renders items incrementally in a non-blocking way.
The important point with a Flatlist is the reusing of cells so that not all components need to be rendered at the same time. So scrolling is an important part of this. On the other hand two scrollable components inside eachother will make it impossible for the system to know which component should be scrolled.
If there are only 3 items and it should not be scrollable you can just include a list of items inside the Scrollview like this:
const Comp = () => {
return (
<ScrollView contentInsetAdjustmentBehavior="automatic">
<Text>Header</Text>
<Card>
{ data.map((item, index) => {
return (
<Text key={index}>{item.title}</Text>
);
}) }
</Card>
<Text>Footer</Text>
</ScrollView>
);
};
I'm a newbie to react native. I try to add a flatlist to my app.
I hava an array of datas designed like that:
["https://hi.com//image.png", //uri
"hello",
"https://hi.com//image2.png",
"welcome",
"https://hi.com//image3.png",
"great",
../..
]
Problem is that my image shows up but the text in the right side is actually my uri stringyfied.
I think there's something wrong with the keyExtractor:
renderItem =({item}) => {
return(
<View style ={{flex:1, flexDirection:'row'}}>
<Image source ={{uri: item}}/>
<View style ={{flex:1, justifyContent: 'center'}}>
<Text>{item}</Text>
</View>
</View>
)
}
render() {
return (
<View style={styles.mainContainer}>
<FlatList
data= {this.state.dataSource}
keyExtractor={(item,index) => index.toString()}
renderItem= {this.renderItem}
/>
</View>
);
}
your renderItem function loops thru every single element of array, not sure its the best option for your kind of data, maybe try to use something like this instead
const data = [{uri: 'https://link.io', text: 'hello'},{uri: 'http://anotherlink.co', text: 'bye'}]
then inside your renderItem fuction pass data:
<Image source ={{uri: item.uri}}/>
<Text>{item.text}</Text>
on the other hand if your you need to keep flat array, maybe be write some function with modulo of deviding index by 2 and from there get what goes where, but not sure why would you need that apart maybe from codewars challange ;)
good luck, hope this helps
your flatlist render item is trying to access the item in a loop.so everytime it gets looped,you are passing item to Image and item to Text.As #wijuwiju suggested,that is the best way to implement it.try to maintain keys to your data.Then your flatlist will render properly.
Store Item Like this:
profilePicture: 'https://picsum.photos/200',
Set Image Source Like this:
<Image source={{uri:profilePicture}}/>
I am trying to create a chat in React native using a <Flatlist />
Like WhatsApp and other chat apps, the messages start at the bottom.
After fetching the messages from my API, I call
this.myFlatList.scrollToEnd({animated: false});
But it scrolls somewhere in the middle and sometimes with fewer items to the bottom and sometimes it does nothing.
How can I scroll initially to the bottom?
My chat messages have different heights, so I can't calculate the height.
I had similar issue. If you want to have you chat messages start at the bottom, you could set "inverted" to true and display your messages and time tag in an opposite direction.
Check here for "inverted" property for FlatList. https://facebook.github.io/react-native/docs/flatlist#inverted
If you want to have you chat messages start at the top, which is what I am trying to achieve. I could not find a solution in FlatList, because as you said, the heights are different, I could not use getItemLayout which make "scrollToEnd" behave in a strange way.
I follow the approach that #My Mai mentioned, using ScrollView instead and do scrollToEnd({animated: false}) in a setTimeout function. Besides, I added a state to hide the content until scrollToEnd is done, so user would not be seeing any scrolling.
I solved this issue with inverted property and reverse function
https://facebook.github.io/react-native/docs/flatlist#inverted
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
<FlatList
inverted
data={[...data].reverse()}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
You can use this solution in chat component.
I faced the same issue with you and then I moved to use ScrollView.
It is fixed:
componentDidMount() {
setTimeout(() => {
this.scrollView.scrollToEnd();
});
}
<ScrollView ref={(ref) => { this.scrollView = ref; }} style={styles.messages}>
{
messages.map((item, i) => (
<Message
key={i}
direction={item.userType === 'banker' ? 'right' : 'left'}
text={item.message}
name={item.name}
time={item.createdAt}
/>
))
}
</ScrollView>`
Set initialScrollIndex to your data set's length - 1.
I.e.
<Flatlist
data={dataSet}
initialScrollIndex={dataSet.length - 1}
/>
There are two types of 'good' solutions as of 2021.
First one is with timeout, references and useEffect. Here's the full example using Functional Components and Typescript:
// Set the height of every item of the list, to improve perfomance and later use in the getItemLayout
const ITEM_HEIGHT = 100;
// Data that will be displayed in the FlatList
const [data, setData] = React.useState<DataType>();
// The variable that will hold the reference of the FlatList
const flatListRef = React.useRef<FlatList>(null);
// The effect that will always run whenever there's a change to the data
React.useLayoutEffect(() => {
const timeout = setTimeout(() => {
if (flatListRef.current && data && data.length > 0) {
flatListRef.current.scrollToEnd({ animated: true });
}
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [data]);
// Your FlatList component that will receive ref, data and other properties as needed, you also have to use getItemLayout
<FlatList
data={data}
ref={flatListRef}
getItemLayout={(data, index) => {
return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index };
}}
{ ...otherProperties }
/>
With the example above you can have a fluid and animated scroll to bottom. Recommended for when you receive a new message and has to scroll to the bottom, for example.
Apart from this, the second and easier way is by implementing the initialScrollIndex property that will instantly loads the list at the bottom, like that chat apps you mentioned. It will work fine when opening the chat screen for the first time.
Like this:
// No need to use useEffect, timeout and references...
// Just use getItemLayout and initialScrollIndex.
// Set the height of every item of the list, to improve perfomance and later use in the getItemLayout
const ITEM_HEIGHT = 100;
<FlatList
data={data}
getItemLayout={(data, index) => {
return { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index };
}}
{ ...otherProperties }
/>
I found a solution that worked for me 100%
Added the ref flatListRef to my flatlist:
<Flatlist
reference={(ref) => this.flatListRef = ref}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
Then whenever you want to automatically scroll to bottom of the list use:
this.flatListRef._listRef._scrollRef.scrollToEnd({ animating: true });
yes you should access the element _listRef then _scrollRef then call the scrollToEnd 🙄
react-native 0.64.1
react 17.0.2
I've struggled on this as well and found the best possible solution for me that renders without a glitch is:
Use inverted={-1} props
Reverse the order of messages objects inside my array with data={MyArrayofMessages.reverse()} in my case data={this.state.messages.reverse()} using reverse() javascript function.
Stupidly easy and renders instantaneously !
Use inverted={1} and reverse your data by using the JS reverse function. It worked for me
<FlatList contentContainerStyle={{ flex: 1, justifyContent: 'flex-end' }} />
I am guessing that RN cannot guess your layout so it cannot know how much it needs to "move". According to the scroll methods in the docs you might need to implement a getItemLayout function, so RN can tell how much it needs to scroll.
https://facebook.github.io/react-native/docs/flatlist.html#scrolltoend
Guys if you want FlatList scroll to bottom at initial render. Just added inverted={-1} to your FlatList. I have struggle with scroll to bottom for couple of hours but it ends up with inverted={-1}. Don't need to think to much about measure the height of FlatList items dynamically using getItemLayout and initialScrollIndex or whats so ever.
I found a solution that worked for me 100%
let scrollRef = React.useRef(null)
and
<FlatList
ref={(it) => (scrollRef.current = it)}
onContentSizeChange={() =>
scrollRef.current?.scrollToEnd({animated: false})
}
data={data}/>
If you want to display the message inverted, set "inverted" to true in the flat list.
<Flatlist
data={messageData}
inverted={true}
horizontal={false}
/>
If you just want to scroll to the last message, you can use initialScrollIndex
<Flatlist
data={messageData}
initialScrollIndex={messageArray.length - 1}
horizontal={false}
/>
I spent couple of hours struggling with showing the first message on top without being able to calculate the item's height as it contains links and messages. But finally i've been able to...
What i've done is that i wrapped the FlatList in a View, set FlatList as inverted, made it to take all available space and then justified content. So now, conversations with few messages starts at top but when there are multiple messages, they will end on bottom. Something like this:
<View style={ConversationStyle.container}>
<FlatList
data={conversations}
initialNumToRender={10}
renderItem={({ item }) => (
<SmsConversationItem
item={item}
onDelete={onDelete}
/>
)}
keyExtractor={(item) => item.id}
getItemCount={getItemCount}
getItem={getItem}
contentContainerStyle={ConversationStyle.virtualizedListContainer}
inverted // This will make items in reversed order but will make all of them start from bottom
/>
</View>
And my style looks like this:
const ConversationStyle = StyleSheet.create({
container: {
flex: 1
},
virtualizedListContainer: {
flexGrow: 1,
justifyContent: 'flex-end'
}
};
Here is render() function for my simple category list page.
Recently I added pagination for my FlatList View so when the user scrolls to the bottom, onEndReached is called in a certain point(onEndReachedThreshold value length from the bottom), and it will fetch the next categories and concatenate the categories props.
But my problem is onEndReached is called when render() is called In other words, FlatList's onEndReached is triggered before it reach the bottom.
Am I putting wrong value for onEndReachedThreshold? Do you see any problem?
return (
<View style={{ flex:1 }}>
<FlatList
data={this.props.categories}
renderItem={this._renderItem}
keyExtractor={this._keyExtractor}
numColumns={2}
style={{flex: 1, flexDirection: 'row'}}
contentContainerStyle={{justifyContent: 'center'}}
refreshControl={
<RefreshControl
refreshing = {this.state.refreshing}
onRefresh = {()=>this._onRefresh()}
/>
}
// curent value for debug is 0.5
onEndReachedThreshold={0.5} // Tried 0, 0.01, 0.1, 0.7, 50, 100, 700
onEndReached = {({distanceFromEnd})=>{ // problem
console.log(distanceFromEnd) // 607, 878
console.log('reached'); // once, and if I scroll about 14% of the screen,
//it prints reached AGAIN.
this._onEndReachedThreshold()
}}
/>
</View>
)
UPDATE I fetch this.props.categories data here
componentWillMount() {
if(this.props.token) {
this.props.loadCategoryAll(this.props.token);
}
}
Try to implement onMomentumScrollBegin on FlatList :
constructor(props) {
super(props);
this.onEndReachedCalledDuringMomentum = true;
}
...
<FlatList
...
onEndReached={this.onEndReached.bind(this)}
onEndReachedThreshold={0.5}
onMomentumScrollBegin={() => { this.onEndReachedCalledDuringMomentum = false; }}
/>
and modify your onEndReached
onEndReached = ({ distanceFromEnd }) => {
if(!this.onEndReachedCalledDuringMomentum){
this.fetchData();
this.onEndReachedCalledDuringMomentum = true;
}
}
I've got it working with
<Flatlist
...
onEndReached={({ distanceFromEnd }) => {
if (distanceFromEnd < 0) return;
...
}
...
/>
First check if the FlatList is inside a ScrollView or Content of native-base. Then take it outside of it
Actually you don't need to use Content or ScrollView, as FlatList has both ListFooterComponent and ListHeaderComponent.
Though it is not recommended, if you really need to use Flatlist inside ScrollView, then take a look at this answer: https://stackoverflow.com/a/57603742/6170191
After hours of trying different approaches I got it to work by wrapping the Flatlist with a View of fixed height and flex:1.
With this settings, I was able to get onEndReached called once and only after I scroll near the bottom. Here's my code sample:
render() {
const {height} = Dimensions.get('window');
return (
<View style={{flex:1, height:height}}>
<FlatList
data={this.props.trips_uniques}
refreshing={this.props.tripsLoading}
onRefresh={()=> this.props.getTripsWatcher()}
onEndReached={()=>this.props.getMoreTripsWatcher()}
onEndReachedThreshold={0.5}
renderItem={({item}) => (
<View style={Style.card}>
...
...
</View>
)}
keyExtractor={item => item.trip_id}
/>
</View>
)
}
My onEndReached() function just calls the API and updates my data. It doesn't do any calculations with regards to distance to bottom or threshold
Most of the times, this error is caused because of an incorrect use of onEndReachedThreashold, which also depends of the number of items you are rendering (more items, more scroll size).
Try to follow this logic:
If 10 items cover your screen, and you are rendering 20 items on each scroll, then set onEndReachedThreashold to 0.8.
If 2 or 3 items cover your screen, and you are rendering 10 items on each scroll, then set onEndReachedThreashold to 0.5.
Also, use initialNumToRender = numItems. For some reason, using this FlatList prop helps to reduce the chance of multiple onEndReached calls.
Just play with onEndReachedThreashold value.
Other times, this error is produced because of nesting scroll views. Do not put your FlatList inside of a ScrollView. Instead, take use of the FlatList header and footer props.
For both solutions, I suggest to set the FlatList style and contentContainerStyle to { flexGrow: 1 }.
Remove every Scrollable View inside your FlatList
If you want to show 3 or 4 records and want to load the next data just when you reach the end. Set onEndReachedThreshold to 0 or 0.1.
Maybe You can bypass this FlatList bug by incrementing your page before doing async call, and then you will fetch data on every onEndReached fiers and not get errors about duplicate keys
(as of NOV19)
Keep flatlist as the only component inside of a single view
Set style of that single view from dimensions like
{{flex: 1, height: Dimensions.get('window').height}}
If FlatList is on another FlatList or ScrollView the onEndReached call immediately when rendered component to resolve that problem doesn't wrap FlatList with another.
A bit late but I just ran into this issue and I fixed it by passing to my <FlatList/> the initialNumToRender prop. This prop is 10 by default so if you don't set it and your screen shows more than 10 items on the initial render, it is going to trigger onEndReached since it has passed the 10th element.
initialNumToRender should probably be the same as the amount of elements you fetch per page.
I have a <FlatList> (from react-native) inside an <Overlay> (from react-native-elements.) I have the problem of onEndReached being executed as soon as the component is rendered for the 1st time and before the user does anything.
The problem was resolved by using <Modal> (from react-native), instead of <Overlay>.
If you are using hooks, here you can find the hook version of #Ilario answer:
const onEndReachedCalledDuringMomentum = useRef(true)
onEndReachedHandler = ({ distanceFromEnd }) => {
if(!onEndReachedCalledDuringMomentum.current){
fetchData()
onEndReachedCalledDuringMomentum.current = true
}
}
<FlatList
...
onEndReached={onEndReachedHandler}
onEndReachedThreshold={0.7}
onMomentumScrollBegin={() => { onEndReachedCalledDuringMomentum.current = false }}
/>
This simple solution worked for me. Note the "refreshing" state is controlled by an async API call in a useEffect hook to retrieve data for the FlatList.
const onEndReachedHandler = () => {
if (!refreshing) ...
}
<FlatList
...
data={mydata}
onEndReached={onEndReachedHandler}
onEndReachedThreshold={0.7}
refreshing={refreshing}
/>
I struggled around the whole day but the issue that I was getting is, I am using FlatList inside ScrollView. So, Remove Scrollview & then use Flatlist independently. This will solve my problem.
From my experience, you can simply utilize onEndReachedThreshold props in your FlatList or SectionList and pass a very very small number like 0.001 to it.
onEndReachedThreshold={0.001}
According to docs for FlatList, onEndReachedThreshold is units of length from the bottom in list items.
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. For example, a value of 0.5 will trigger
onEndReached when the end of the content is within half the visible
length of the list.
Thus, a very small value like 0.001 helps you to make sure that onEndReached is only gonna be called when the end of the content is within the very end of the visible length of the list.
Hope this helps :) Sorry for bad English.
The solution is simpler than anyone would think.
Just add an !isLoading condition for fetch calling. It works for me:
onEndReached={() => {
if (!isLoading) {
fetchProducts();
}
}}
And the full code with ScrollView and FlatList:
<ScrollView
horizontal={true}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
style={styles.mainContainer}>
<FlatList
ListHeaderComponent={TableHeader}
data={displayedProducts}
renderItem={(item: ListRenderItemInfo<IProduct>) => TableRow(item, showDeleteModal)}
keyExtractor={keyExtractor}
ListFooterComponent={<Loading loadingText={'Loading products...'} />}
onEndReached={() => {
if (!isLoading) {
fetchProducts();
}
}}
onEndReachedThreshold={0.8}
stickyHeaderIndices={[0]}
/>
</ScrollView>
I have solved it with using debounce from lodash. Firstly, I import debounce from 'lodash.debounce'. Then I use debounce for load more function with 500 ms interval
<Flatlist
onEndReached = {debounce(this._onLoadMore, 500)}
/>