how to make flatlist component of react-native scroll to bottom when its first rendered? - react-native

I am trying to build a Chat Application using react-native and using flatlist to display the messages. And i want my list to stay at bottom always can anyone plzz suggest me a solution.

Basically you need to use scrollToEnd on flatList..
First: create a flatList reference with:
ref={ (ref) => { this.myFlatListRef = ref } }
Second: add onContentSizeChange listener on flatList
onContentSizeChange={ () => { this.myFlatListRef.scrollToEnd({animated:true}) } }
Third: add onLayout listener on flatList for scroll to bottom when keyboard is showed.
onLayout={ () => { this.myFlatListRef.scrollToEnd({animated:true}) } }
eg:
<FlatList
ref={ (ref) => { this.myFlatListRef = ref } }
onContentSizeChange={ () => { this.myFlatListRef.scrollToEnd({animated:true}) } }
onLayout={ () => { this.myFlatListRef.scrollToEnd({animated:true}) } }
data={this.state.messages}
renderItem={ ({item}) => <Item data={item} /> }
/>

the simplest way to do this is to use inverted props in the flatlist
this allow us to render list up-side-down
for more ref. vidit https://facebook.github.io/react-native/docs/flatlist#inverted

Here, you can do this -
const messageData = this.props.convo
return (
<View style={{ flex: 1 }}>
<FlatList
inverted={-1}
style={styles.list}
extraData={this.props}
data={messageData}
keyExtractor = {(item) => {
return item.id;
}}
renderItem=
{this.renderItem}
/>
after that, you will get an inverted list which will have the topmost elements in the list, so for fixing this you can add reverse() in your flatlist data or in your reducer. Like this-
data={messageData.reverse()} or in reducer return { ...state, chats: action.payload.reverse() }

You can give a refto FlatList and access certain flatList properties that can scroll at particular position. Here below resolution should work.
setTimeout(() => this.refs.flatList.scrollToEnd(), 200)
ScrollToEnd React native

Related

react-native : how to sort data before screen is drawn?

I call data by using useQuery and gql.
const SEE_ALL_FEED_ORDER = gql`
query seeAllFeedOrder {
seeAllFeedOrder {
id
name
avatar
directFeedNumber
}
}
`;
const { data: allFeedData, loading: allFeedDataLoading,
refetch: allFeedRefetch } = useQuery(SEE_ALL_FEED_ORDER);
I named result data as allFeedData as above.
And I need to sort this allFeedData before screen is shown up.
So I use useEffect and useState.
const [flatlistdata, setFlatlistdata] = useState([]);
useEffect(() => {
if (!allFeedDataLoading) {
setFlatlistdata(
[...allFeedData.seeAllFeedOrder].sort(function (a, b) {
return b.directFeedNumber - a.directFeedNumber;
})
);
}
}, []);
So if this query loading is finished, then I put sorted data to flatlistdata by using setFlatlistdata.
And with this flatlistdata, I run flatlist.
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/>
But when I click screen, undefined is not an object(evaluating 'allFeedData.seeAllFeedOrder' error comes.
which means I couldn't call allFeedData.
I think this might happen screen is drawn before data is sorted? is that right?
So I also give condition to Flatlist as below.
flatlistdata === [] ? (
<View>
<ActivityIndicator size={30}></ActivityIndicator>
</View>
) :
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/> }
or
allFeedDataLoading ? (
<View>
<ActivityIndicator size={30}></ActivityIndicator>
</View>
) :
<FlatList
data={flatlistdata}
keyExtractor={(item) => item.id}
renderItem={RankRow}
refreshing={refreshing}
onRefresh={refresh}
/> }
I intended to draw ActivityIndicator screen first before data is sorted and put to flatlistdata and then proper screen show up, but it throws same error.
What is the problem? and how can I fix this ?
So I use useEffect and useState.
why would you need that? the sorted data is derived state that doesn't need to be managed separately:
const { data: allFeedData } = useQuery(SEE_ALL_FEED_ORDER);
const sortedData = allFeedData ? [...allFeedData.seeAllFeedOrder].sort(...) : []
then just pass sortedData to the FlatList. If it turns out that sorting is slow or that referential identity is important, you can wrap the sorting in useMemo. I also have a blog post on this topic: https://tkdodo.eu/blog/dont-over-use-state

Best method to optimize performance of FlatList Items

This a simple FlatList:
class Products ..
render() {
return (
<FlatList
renderItem={this._renderItem}
);
}
I want to create a list of items and navigate to Detail Page by onPress items.
Can Please tell me which method is better?
Method 1:
Insert navigate to Detail page in child component(CardProduct component) like this:
_renderItem = ({item}) => (
<CardProduct
id={item.id}
title={item.title}
/>
);
and in CardProduct component:
render() {
const { id,title } = this.props;
return (
<Card style={{flex:1}}>
<CardItem cardBody button onPress={() => this.props.navigation.navigate('Details',{productId:id})}>
...
);
}
Method 2:
Insert navigate to Detail page in current component(Products component) like this:
_onPressItem = (id: string) => {
this.props.navigation.navigate('Details',{productId:id});
};
_renderItem = ({item}) => (
<CardProduct
id={item.id}
title={item.title}
onPressItem={this._onPressItem}
/>
);
and in CardProduct component:
_onPress = () => {
this.props.onPressItem(this.props.id);
};
render() {
const { id,title } = this.props;
return (
<Card style={{flex:1}}>
<CardItem cardBody button onPress={this._onPress}>
...
);
}
I used to do the method 1, but I read this guide.
Short answer:
You should go for method2.
Explanation:
In method1 you are using an arrow function in CardItem's onPress, so everytime CardProduct is re-rendered a new reference of onPress is created, which forces CardItem to re-render, even if all the other props are staying the same. In method 2 you are binding the function to context, which won't force a re-rendering of the CardItem.
By the way, in general it is a good idea to prevent the usage of arrow functions in render().
One step for performance optimization in react-native flatlist, is using a stateless functional component for the renderItem. and you should always give each item a unique key.

numColumns breaks scrollToIndex react native FlatList

I'm using react native's FlatList to display a list of items, and also check which items are currently viewable. In my items there's one item which is marked mostUsed if the item is not viewable I display a link at the top, the user can click that and scroll to that item, using scrollToIndex. scrollToIndex works well without setting numColumns, when I set numColumns={2} I get scrollToIndex out of range: 9 vs 4 error.
setScrollIndex = () => {
if (this.state.scrollIndex !== 0) {
return;
}
for (let i = 0; i < this.state.items.length; i++) {
const items = this.state.items;
if (items[i] && items[i].mostUsed) {
this.setState({
scrollIndex: i,
});
}
}
};
// scroll to index function
scrollToIndex = () => {
this.flatListRef.scrollToIndex({
animated: true,
index: this.state.scrollIndex,
});
};
<FlatList
data={this.state.items}
numColumns={2}
keyExtractor={(item, index) => index.toString()}
ref={ref => {
this.flatListRef = ref;
}}
showsVerticalScrollIndicator={false}
onViewableItemsChanged={this.handleViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
renderItem={({ item, index }) => (
<Card
title={item.title}
description={item.description}
mostUsed={item.mostUsed}
style={{ width: item.width }}
/>
)}
/>
expo snack
Looks like FlatList's scrollToIndex changes the way it views index if numColumns is higher then 1.
It may be more correct to call it scrollToRowIndex since it does not work on item index in case of multiple columns.
For your case this worked for me on expo:
scrollToIndex = () => {
this.flatListRef.scrollToIndex({
animated: true,
index: Math.floor(this.state.scrollIndex / numColumns),
});
};

How to make a Chat list like Facebook Messenger in React Native

I'm making a Chat app in React Native, in my app I have a Flatlist to show a message. When user pull the list, it will get new data and add to list. But the list will be re-render and scroll to start item. Is there anyway to make it still get data but stay in current position, like Facebook Messenger ?
I'm using FlatList like this :
<FlatList
refreshControl={
<RefreshControl
refreshing = {this.state.refreshing}
onRefresh = {this.addMessageToList}
/>
}
keyExtractor={(item, index) => index.toString()}
ref="flatList"
style={{ flex: 1 }}
data={newList}
renderItem={({ item, index }) => {
return (
<ChatContentItems item={item} index={index} parentFlatList={this}>
</ChatContentItems>
);
}}>
</FlatList>
Use onEndReached event to handle when user reached the end.
<FlatList
data={this.state.latestData}
keyExtractor={item => item.id.toString() }
renderItem={({item}) => <JobsListCell item={item}/>}
onEndReached={this.loadMore.bind(this)} // add this
onEndReachedThreshold={0.3} /> // and this. change according to your preference.
Since you have the full array with you, slice and append the next items to the latest array.
loadMore() {
let { latestData, fetchedData, incrementingAmount } = this.state;
if (latestData.length < fetchedData.length) {
this.setState({
latestData: [...latestData, ...fetchedData.slice(latestData.length, latestData.length + incrementingAmount)]
})
}
}
You can use scrollToEnd or scrollToItem after your addMessageToList completes
Also, ref strings are deprecated. You can use
ref={ (flatList) => this.flatList = flatList }

React-Native re-render Flatlist

I'm having trouble keeping the data in my Flatlist after coming back from another page. My scenario is as follows:
User goes to homepage and scrolls through 20 items
User clicks their profile tab changing page using react-native-router-flux
User clicks the home tab taking them back to the list however the list re-renders and starts from the top.
How can I stop this re-rendering and fetching the same data again?
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteReuest gets my data from firebase in batches of 5 and sets data: []
data: [...this.state.data, ...results]
I've tried using the below but not sure if this is correct, when i navigate away and back the data re-renders. I want to keep the data so the page will be exactly the same as when it was left.
shouldComponentUpdate(nextProps, nextState) {
if (JSON.stringify(this.state.data) !== JSON.stringify(nextState.data)) {
return true;
}
return false;
}
My flatlist:
<View>
<FlatList
scrollsToTop={false}
ref={(ref) => { this.flatListRef = ref; }}
showsHorizontalScrollIndicator={false}
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>
Thanks for any help!
Coded long back for the dumb project, maybe this can help you
The View: used onLayout Prop for getting the y-axis
<ScrollView
ref={(ref) => this.scrollTo = ref}
contentContainerStyle={{margin:5,}}
>
<Card onLayout={(event) => this._findHeight(event.nativeEvent.layout, 'personal')}>
<Personal review={true}/>
</Card>
</ScrollView>
The Function: stored the y-axis; here i have used realm db
_findHeight = (e, name) => {
const {x, y, width, height} = e;
this.realm.write(() => {
this.realm.create('yLocation',{names:name,yaxis:y}) :
});
}
The AutoScroll Method: here i have used scrollTo method from ScrollView you can use any method using their ref
_scrollTo = (y) => {
this.scrollTo.scrollTo({x:0,y:y,animated:true});
}
Note : Call _scrollTo method in componentDidMount