ScrollToIndex in React-Native - react-native

In my application, I want to use scrollToIndex() which is a method of the FlatList component.
render() {
let { posts, featuredWorkouts, mainFeedIsLoading } = this.props;
let mainFeed = <SwiperContainer featuredWorkouts={featuredWorkouts} />;
let mainFeedData = (
<View>
<View style={{ marginBottom: 5 }}>
<SwiperContainer featuredWorkouts={featuredWorkouts} />
</View>
<FlatList
data={posts}
scrollToIndex={5}
extraData={[posts, mainFeedIsLoading]}
renderItem={item => this.renderPost(item)}
keyExtractor={item => item.shareId}
/>
</View>
);
if (mainFeedIsLoading) {
mainFeedData = (
<View style={styles.screenLoader}>
<ScreenLoader color="lightblue" size="large" />
</View>
);
}
return (
<View>
<ScrollView>{mainFeedData}</ScrollView>
</View>
);
}
As an example, when the page loads, I want to go to the 10th index. How can I achieve this? I tried the scrollToIndex() and it did not work.

you have to add ref to your flat list.
ref={(ref) => { this.list = ref; }}
Also you have to add to scroll index to your componentdidmount.
componentDidMount() {
this.list.scrollToIndex({animated: true, index: tempIndex, viewOffset:0,viewPosition:0});
}
Also you can use:
initialScrollIndex

Well in my case I have to do this to make scrolltoindex work properly,
1.add reference to flatlist:
`
ref={ref => {
this.flatListRef = ref;
}}
`
2. Call scrollToIndex() with timeout:
`
setTimeout(() => {
this.flatListRef.scrollToIndex({
animated: true,
index: this.state.pressedIndex
});
}, 500);
`
3. then call function again inside flatlist
onScrollToIndexFailed={() =>
{
this.scrollToIndex();
}
}

Related

Remove an item from FlatList render wrong items

I know that it has been asked several times, but in all the other threads the problem is that the author is manipulating the state directly - which I don't (hopefully).
I've got an array of posts that I get from the DB.
I want to be able to filter this array according to the tags each post has. In order to do so, I'm filtering the array, saving the result in a temp array, and then setting another array that holds the current posts to display again using useState. The filter works properly.
The list of posts is rendered in a FlatList
<FlatList
data={filteredPosts}
extraData={refreshFlat}
style={globalStyles.feed}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
refreshing={refreshing}
onRefresh={handleRefresh}
ListEmptyComponent={() => {
return (
<View>
<Text style={globalStyles.be_first}>
נראה שאין מה להציג כרגע..
</Text>
</View>
);
}}
ItemSeparatorComponent={() => {
return <View style={{ height: 12 }}></View>;
}}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={getHeader}
/>
But when I re-render the list, the wrong items are shown.
For example, if the list contains only one post after the filter, the FlatList will display only the first item of the original list.
Just to make clear, the item is the right item, I used console.log both outside and inside the Post component to validate it.
postsList - Holds the original list
filteredPosts - Holds the current posts that I want to display
refreshFlat - I tried to force it to refresh using extraData
The complete component:
import { Text, Pressable, FlatList, View, Modal } from "react-native";
import { globalStyles } from "../../styles/global";
import React, { useState, useEffect } from "react";
import Post from "../../API/Post";
import { useData } from "../../AuthProvider/UserDataProvider";
const FeedScreen = ({ navigation, route }) => {
const [refreshing, setRefreshing] = useState(true);
const { getPosts, tagsList, getTags } = useData();
const [postsList, setPostsList] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [selectedTags, setSelectedTags] = useState([]);
const [filteredPosts, setFilteredPosts] = useState([]);
const [refreshFlat, setRefreshFlat] = useState(false);
const handleRefresh = () => {
getPosts()
.then((posts) => {
setPostsList(posts);
setFilteredPosts(posts);
setSelectedTags([]);
setRefreshing(false);
})
.catch(console.error);
};
const handleSelectTag = (tag) => {
if (selectedTags.includes(tag)) {
const temp = selectedTags.filter((currTag) => currTag !== tag);
setSelectedTags(temp);
} else {
setSelectedTags((prev) => [...prev, tag]);
}
};
const filterPosts = (tags) => {
if (tags.length === 0) return setFilteredPosts([...postsList]);
const temp = postsList.filter((post) =>
post.data.tags.some((t) => tags.includes(t))
);
console.log(temp);
setFilteredPosts(temp);
setRefreshFlat((prev) => !prev);
};
const getHeader = () => {
return (
<View>
<Modal
visible={modalVisible}
animationType="slide"
onRequestClose={() => {
setModalVisible((prev) => !prev);
}}
>
<View>
<FlatList
data={tagsList}
renderItem={({ item }) => (
<Pressable
style={{
backgroundColor: selectedTags.includes(item.name)
? "green"
: "#EAE7E6",
padding: 5,
margin: 5,
}}
onPress={() => handleSelectTag(item.name)}
>
<Text>{item.name}</Text>
</Pressable>
)}
numColumns={3}
ListEmptyComponent={() => {
return (
<View>
<Text style={globalStyles.be_first}>
נראה שאין מה להציג כרגע..
</Text>
</View>
);
}}
ItemSeparatorComponent={() => {
return <View style={{ height: 12 }}></View>;
}}
keyExtractor={(item, index) => index.toString()}
/>
</View>
<Pressable
onPress={() => {
filterPosts(selectedTags);
setModalVisible(false);
}}
style={{ marginLeft: 10, width: 50, height: 50 }}
>
<Text>{"סנן"}</Text>
</Pressable>
</Modal>
<Pressable
onPress={() => setModalVisible(true)}
style={{ width: "100%", height: 50 }}
>
<Text>{"open modal"}</Text>
</Pressable>
</View>
);
};
useEffect(() => {
getPosts()
.then((posts) => {
setPostsList(posts);
setFilteredPosts(posts);
setRefreshing(false);
})
.catch(console.error);
getTags();
return;
}, []);
return (
<View style={{ flex: 1 }}>
<FlatList
data={filteredPosts}
extraData={refreshFlat}
style={globalStyles.feed}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
refreshing={refreshing}
onRefresh={handleRefresh}
ListEmptyComponent={() => {
return (
<View>
<Text style={globalStyles.be_first}>
נראה שאין מה להציג כרגע..
</Text>
</View>
);
}}
ItemSeparatorComponent={() => {
return <View style={{ height: 12 }}></View>;
}}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={getHeader}
/>
<Pressable
title="edit"
onPress={() => {
navigation.navigate("CreateProject");
}}
style={globalStyles.plus_btn}
>
<Text style={globalStyles.plus_btn_text}>+</Text>
</Pressable>
</View>
);
};
export default FeedScreen;
Maybe it's the key extractor issue? You are using the index of the array as the key and it could confuse the flatlist when you update the data.

react-native FlatList scroll to bottom for chat app

I've made a chat app, and for rendering messages flatlist is used. But the problem is tried to scroll to the end of the screen every time the page is loaded, but it fails to do so. I've tried inverted props, but nothing happened, only the list got inverted.
Even played with ref to make it auto-scroll to the bottom, but nothing happened.
<FlatList
ref="flatList"
onContentSizeChange={() =>
this.refs.flatList.scrollToEnd()}
contentContainerStyle={{
marginBottom:
verticalScale(200)
}}
style={styles.list}
data={this.state.messages}
/>
How to make it scroll to the bottom the screen or scroll to the last index of the message when rendered?
(UPDATE)
IT WAS AN ISSUE WITH THE <Content/> component i used which belongs to native-base . Upon removing and replacing it with a <View/> it works perfectly fine.
Also, for chat based app the inverted prop in Flatlist is the way to implement in right way.
I've added the way i managed to scroll in the answer below. If you simply want your app to display the last item in the list and stays there, you can use inverted
You should use ref like this:
export default class MyAwesomeComponent extends React.Component {
FlatListRef = null; // add a member to hold the flatlist ref
render() {
return (
<FlatList
ref={ref => (this.FlatListRef = ref)} // assign the flatlist's ref to your component's FlatListRef...
onContentSizeChange={() => this.FlatListRef.scrollToEnd()} // scroll it
contentContainerStyle={{marginBottom: verticalScale(200)}}
style={styles.list}
data={this.state.messages}
/>
);
}
}
prueba esto
return (
<View style={{flex: 1}}>
<KeyboardAvoidingView
behavior="padding"
style={styles.keyboard}
keyboardVerticalOffset={height - 1000}>
<FlatList
ref={ref => (this.FlatListRef = ref)}
onContentSizeChange={() => this.FlatListRef.scrollToEnd()} // scroll it
// contentContainerStyle={{marginBottom: verticalScale(200)}}
// keyboardShouldPersistTaps='always'
style={styles.list}
extraData={this.state}
data={this.state.messages}
keyExtractor={item => {
return item.id;
}}
renderItem={e => this._renderItem(e)}
/>
<View style={styles.input}>
<TextInput
// style={{flex: 1}}
value={msg}
placeholderTextColor="#000"
onChangeText={msg => this.setState({msg: msg})}
blurOnSubmit={false}
onSubmitEditing={() => this.send()}
placeholder="Escribe el mensaje"
returnKeyType="send"
/>
</View>
</KeyboardAvoidingView>
</View>
);
You can use Javascript method to reverse to show your messages from end
messages.reverse()
scrollToListPosition = (index) => {
const itemOffset = this.getItemOffset(index)
this.flatListRef.scrollToOffset({ animated: false, offset: itemOffset })
}
getItemOffset = (index) => {
let heightsum = 0
for (i = 0; i < index; i++) {
heightsum = heightsum + this.itemHeight[i]
}
return heightsum
}
render(){
return (
<FlatList
ref={(ref) => { this.flatListRef = ref; }}
data={postList}
keyExtractor={(item, index) => item._id}
horizontal={false}
extraData={this.state}
keyboardShouldPersistTaps='always'
refreshing={this.props.isRefreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
getItemLayout={(data, index) => (
{ length: this.getLength(index), offset: this.getLength(index) * index, index }
)}
renderItem={({ item, index }) => {
return (
<View onLayout={(event) => {
var { height } = event.nativeEvent.layout;
this.itemHeight[index] = height
}}
>
<ListCommon
key={index}
item={item}
index={index}
parentFlatList={this}
data={item}
instance={this.props.commanAction}
tag={this.state.tag}
changeRoute={this.props.changeRoute}
/>
</View>
);
}}
/>
)
}
getLength = (index) => {
if (this.itemHeight[index] === undefined) {
return 0;
}
return this.itemHeight[index]
}
Here is how i solved it:
export default class Test extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
setTimeout(() => {
this.FlatListRef.scrollToEnd();
}, 1500);
}
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
data={[1, 2, 3, 4, 5, 6, 7, 8]}
ref={(ref) => (this.FlatListRef = ref)}
renderItem={({ item }) => {
return (
<View
style={{
height: 140,
width: 400,
backgroundColor: "yellow",
alignItems: "center",
justifyContent: "center",
}}
>
<Text>{item}</Text>
</View>
);
}}
/>
</View>
);
}
}

React Native FlatList Delete Item

I want to delete item from the FlatList. However, the solution from here is not working for my code. When I run my code, I am getting error such as 'index is not defined'.
I have yet to find other post regarding this issue so any help would be greatly appreciated.
Code snippet provided below:
export default class FrCreateScreen extends Component {
constructor(props) {
super(props);
this.state = {
//new timeSlots
timeSlots: [],
}
}
setEndTime = (event, appointmentEndTime, textEndTime) => {
appointmentEndTime = appointmentEndTime || this.state.appointmentEndTime;
textEndTime = textEndTime || moment(appointmentEndTime).format('h:mm a').toString();
this.setState({
showEndTime: Platform.OS === 'ios' ? true : false,
appointmentEndTime,
textEndTime,
timeSlots: [
...this.state.timeSlots,
{
apptdate: this.state.textAppointmentDate,
appttime: this.state.textAppointmentTime,
endTime: textEndTime,
}
],
});
}
deleteDateTime = (id) => {
const filteredData = this.state.timeSlots.filter(item => item.id !== id);
this.setState({ timeSlots: filteredData });
}
render() {
return (
<ScrollView>
...
<View>
<FlatList
data={this.state.timeSlots}
keyExtractor={({ id }, index) => index.toString()}
renderItem={({ item }) => {
return (
<View style={styles.containerList}>
...
<TouchableOpacity onPress={() => this.deleteDateTime(index)}>
<Feather name="trash" style={styles.icon} />
</TouchableOpacity>
</View>
</View>
);
}}
/>
</View>
</ScrollView>
)
};
};
Screenshot below:
I think you need to add index inside renderItem { item, index }
renderItem = {({ item, index }) => {
return (
<View style={styles.containerList}>
<TouchableOpacity onPress={() => this.deleteDateTime(index)}>
<Feather name="trash" style={styles.icon} />
</TouchableOpacity>
</View>
);
}}
While rendering in map function , it provides the index too, so try adding that.
renderItem={({ item,index }) => {
return (
<View style={styles.containerList}>
...
<TouchableOpacity onPress={() => this.deleteDateTime(index)}>
<Feather name="trash" style={styles.icon} />
</TouchableOpacity>
</View>
</View>
);
}}
Hope it helps
ok fixed. on touchable onPress, the argument should be 'item.index' instead of 'index'.
here's the correct code:
renderItem={({ item,index }) => {
return (
<View style={styles.containerList}>
...
<TouchableOpacity onPress={() => this.deleteDateTime(item.index)}>
<Feather name="trash" style={styles.icon} />
</TouchableOpacity>
</View>
</View>
);
}}

How to fetch data from api and set to image slider?

I am new to reactnative and I am trying to make the imagesilder. I am trying to get data from the api and set it to the imageslider. I have to get the image tag from the api and set it to the imageslider.My api is as follows:
[
{
"title":"Taylor Swift",
"artist":"Taylor Swift",
"url":"https://www.amazon.com/Taylor-Swift/dp/B0014I4KH6",
"image":"https://images-na.ssl-images-amazon.com/images/I/61McsadO1OL.jpg",
"thumbnail_image":"https://i.imgur.com/K3KJ3w4h.jpg"
},
{
"title":"Fearless",
"artist":"Taylor Swift",
"url":"https://www.amazon.com/Fearless-Enhanced-Taylor-Swift/dp/B001EYGOEM",
"image":"https://images-na.ssl-images-amazon.com/images/I/51qmhXWZBxL.jpg",
"thumbnail_image":"https://i.imgur.com/K3KJ3w4h.jpg"
}
]
I cant get the image data from the api and to render it inside the render function. I have implemented as follows:
export default class ViewpagerP extends Component {
constructor(props) {
super(props);
this.state = { isLoading: true, images: [], data: [] }
}
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response =>response.json())
.then((data)=>{for (let index = 0; index < data.length; index++) {
this.setState({
isLoading: false,
data: data[index]
})
}})
}
render() {
const { images } = this.state;
return (
<View style={styles.container}>
<ImageSlider style={styles.viewPagerStyle}
loopBothSides
autoPlayWithInterval={1000}
images={images}
customSlide={({ index, item, style, width }) => (
<View key={index} style={style}>
<Image source={{ uri: item }} style={styles.customImage} />
</View>
)}
/>
</View>
)
}
}
As you already have images in state, utilize that only.
.then((data)=>{
this.setState({
isLoading: false,
images:data
})
})
Now you have your response within this.state.images, so just need to do one change which is inside customSlide
customSlide={({ index, item, style, width }) => (
<View key={index} style={style}>
<Image source={{ uri: item.image }} style={styles.customImage} />
</View>
)}
item will give you whole single node we need to extract image from that element, so we will write item.image
Use the code below and it will works, since you forgot to add data state in you image slider library, that's why you are not getting your desired result
export default class ViewpagerP extends Component {
constructor(props) {
super(props);
this.state = { isLoading: true, images: [], data: [] }
}
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
data: responseJson
})
})
}
render() {
return (
<View style={styles.container}>
<ImageSlider style={styles.viewPagerStyle}
loopBothSides
autoPlayWithInterval={1000}
images={this.state.data}
customSlide={({ index, item, style, width }) => (
<View key={index} style={style}>
<Image source={{ uri: item.image }} style={styles.customImage} />
</View>
)}
/>
</View>
);
}
}
I solved it
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then((response => this.setState({
isLoading: false,
data: response.data,
}))
)
}
And in render
if (this.state.isLoading) {
return (
<View style={{ flex: 1, padding: 20 }}>
<ActivityIndicator />
</View>
)
}
console.log(this.state.data)
return (
<View style={styles.container}>
<ImageSlider style={styles.viewPagerStyle}
loopBothSides
autoPlayWithInterval={6000}
images={this.state.data}
customSlide={({ index, item, style, width }) => (
<View key={index} style={[style]}>
<Image source={{ uri: item.image }} style={styles.customImage} />
</View>
)}
/>
</View>
);
}

Implement onPress on Flatlist item

I am trying to send the data of the flatlist items when clicked and set to another class.The ontouch is working but I am having the error below in the image. Also how can I send the data of api to the other class and get from another class? I have implemented as follows:
export default class FlatSpeakers extends Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [],selectedItem: null, }
const { navigate } = this.props.navigation;
}
onPressItem = () => {
navigate('SpeakersClick')
};
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(res => {
this.setState({
isLoading: false,
data: res.data,
})
})
}
renderItem({ item }) {
return (
<TouchableOpacity onPress={()=>this.onPressItem(item)} >
<Card>
<CardSection>
<View style={styles.thumbnailContainerStyle}>
<Image
style={styles.thumbnailStyle}
source={{ uri: item.image }}
/>
</View>
<View style={styles.headerContentStyle}>
<Text style={styles.headerTextStyle}>{item.title}</Text>
<Text>{item.artist}</Text>
</View>
</CardSection>
</Card>
</TouchableOpacity>
)
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, padding: 20 }}>
<ActivityIndicator />
</View>
)
}
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={(item, index) => index}
onPress={this.onPressItem}
/>
</View>
);
}
}
Problem in your code is, that you are calling same method from two sides - on one side you are passing arguments in it on another side you are not passing arguments. If you wan't to have both cases covered you should change you onPressFunction, to accept arguments - in your case item:
onPressItem = (item) => {
navigate('SpeakersClick')
};
try to put this.onPressItem = this.onPressItem.bind(this) on constructor(props)