Implement pull to refresh FlatList in React native - react-native

Flatlist code
class HomeScreen extends Component {
state = { refreshing: false }
_renderItem = ({ item }) => <ImageGrid item={item} />
_handleRefresh = () => {
};
render() {
const { data } = this.props;
if (data.loading) {
return (
<Root>
<Loading size="large" />
</Root>
)
}
//render
return (
<FlatList
contentContainerStyle={{ alignSelf: 'stretch' }}
data={data.getPosts}
keyExtractor={item => item._id}
renderItem={this._renderItem}
numColumns={3}
refreshing={this.state.refreshing}
onRefresh={this._handleRefresh}
/>
);
}
}
implemented with class component

Make changes:
_handelRefresh=async()=>{
this.setState({refreshing: true})
this.YOURAPIFunction();
this.setState({refreshing: false})
}
<FlatList
contentContainerStyle={{ alignSelf: 'stretch' }}
data={data.getPosts}
keyExtractor={item => item._id}
renderItem={this._renderItem}
numColumns={3}
refreshing={this.state.refreshing}
onRefresh={this._handleRefresh}
/>

Related

How to add to export default class extends Component - ({navigation})?

I have a problem with adding the {navigation} parameter to the export default class extends Component, I need it for the FlatList
How can I add it here?
export default class ENews extends Component {
render() {
return (
<View style={styles.main}>
<StatusBar style='auto'/>
<FlatList
data={this.state.data}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isFetching}
initialNumToRender={4}
contentContainerStyle={{ alignSelf: 'stretch' }}
keyExtractor={({ id }, index) => id}
renderItem={({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('FullNews', item)}>
</TouchableOpacity>
)} />
</View>
);
}}
You need to get navigation from props you can either deconstruct this.props and get navigation like this
const { navigation } = this.props;
or you can directly use it like this
this.props.navigation.navigate('FullNews', item)
But I suggest using deconstructing props
export default class ENews extends Component {
render() {
const { navigation } = this.props;
return (
<View style={styles.main}>
<StatusBar style='auto'/>
<FlatList
data={this.state.data}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isFetching}
initialNumToRender={4}
contentContainerStyle={{ alignSelf: 'stretch' }}
keyExtractor={({ id }, index) => id}
renderItem={({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('FullNews', item)}>
</TouchableOpacity>
)} />
</View>
);
}}
Parent class
export default class ComponentName extends Component {
render() {
return (
<>
...other component
<ENews {...this.props}></ENews>
</>
);
}
}
you Component
export default class ENews extends Component {
render() {
const {navigation} = this.props; // here you destructuring you props
return (
<View style={styles.main}>
<StatusBar style="auto" />
<FlatList
data={this.state.data}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isFetching}
initialNumToRender={4}
contentContainerStyle={{alignSelf: 'stretch'}}
keyExtractor={({id}, index) => id}
renderItem={({item}) => (
<TouchableOpacity
onPress={() => navigation.navigate('FullNews', item)}
/>
)}
/>
</View>
);
}
}
change as above in your code

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.

Is there an optimal way to disable parent scroll

I am trying to disable parent scroll, when scrolling up/down in the child component (child component is a horizontal FlatList). Is there an optimal way?
<ScrollView scrollEnabled={scrollEnabled}>
<Header />
<FlatList
horizontal={true}
data={cards}
renderItem={() => (
<Pressable onPressIn={() => setScrollEnabled(false)} onPressOut={() => setScrollEnabled(true)}>
<ScrollingCard />
</Pressable>
)}
keyExtractor={(item) => item.toString()}
/>
{cards.map((item) => <ScrollingCard key={`vertical-${item}`} item={item} type="Vertical" />)}
</ScrollView>
Declare a state:-
const [data, setData] = useState({
username: "Mark White",
enableScrollViewScroll: true
})
Handle state when scroll:-
const onEnableScroll = (value) => {
setData({
...data,
enableScrollViewScroll: value
})
}
Inside component:-
<ScrollView
showsVerticalScrollIndicator={false}
scrollEnabled={data.enableScrollViewScroll}>
<View>
<FlatList
onTouchStart={() => {
onEnableScroll(false);
}}
onMomentumScrollEnd={() => {
onEnableScroll(true);
}}
...
/>
</View>
</ScrollView>
export const ScrollEnabledContext = createContext(null);
const Parent = () => {
const ref = useRef(null);
const setIsEnabled = (bool) => {
ref.current?.setNativeProps({ scrollEnabled: bool });
};
return (
<ScrollEnabledContext.Provider value={setIsEnabled}>
<ScrollView ref={ref} nestedScrollEnabled={true}>
<ChildScrollView />
</ScrollView>
</ScrollEnabledContext.Provider>
);
};
const ChildScrollView = () => {
const value = useContext(ScrollEnabledContext);
return (
<ScrollView onTouchStart={() => value(false)} onTouchEnd={() => value(true)}>
....
</ScrollView>
);
};
It works without any re-renders and works way faster than setting state.

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>
);
}
}

ReactNative checkbox inside flatlist

I have checkboxes inside Flatlist like this
constructor(props) {
super(props);
this.state = {
checked: false
}
}
breakfastData = ({item}) => {
return(
<ListItem style={{flex:1 , flexDirection: 'row' , justifyContent:'space-between'}} >
<Text>{item}</Text>
<CheckBox
checked={this.state.checked}
onPress={() => this.setState({checked: !this.state.checked})} style={{ alignSelf: 'flex-end'}} color="#FC7B04" />
</ListItem>
)
}
render(){
return(
<View>
<FlatList
data={this.state.breakfast}
renderItem={ this.breakfastData }
keyExtractor={(item, index) => index}
/>
</View>
)
}
here is a screenshot from the app but the checkboxes don't work when i click any of the check boxes
i just want the user feel that the check box is checked
You must set checked for each item.
constructor(props) {
super(props);
this.state = {
breakfast:[
{id:1,checked:false},
{id:2,checked:false}
]
}
}
onItemPress = (item, index) => {
var tempItem = item
tempItem.checked = !item.checked
const tempArr = [...this.state.breakfast]
tempArr[index] = tempItem
this.setState({
breakfast: tempArr
})
}
breakfastData = ({item}) => {
return(
<ListItem style={{flex:1 , flexDirection: 'row' , justifyContent:'space-between'}} >
<Text>{item}</Text>
<TouchableOpacity onPress={() => {this.onItemPress(item,index)}}>
<CheckBox checked={item.checked} style={{ alignSelf: 'flex-end'}} color="#FC7B04" />
</TouchableOpacity>
</ListItem>
)
}
render(){
return(
<View>
<FlatList
data={this.state.breakfast}
renderItem={ this.breakfastData }
extraData ={ this.state}
keyExtractor={(item, index) => index}
/>
</View>
)
}