I have a profile component which is a tab screen, and here is the code:
class Profile extends React.Component {
constructor(props) {
super(props)
this.state = {
user: this.props.user,
bio: "",
storage_image_uri: '',
postCount: 0,
followerCount: 0,
followingCount: 0,
isLoading: true,
navigation: this.props.navigation,
userUID: Firebase.auth().currentUser.uid,
userPostsArray: []
}
this.firestoreRef =
Firebase.firestore()
.collection('globalPosts')
.where("uid", "==", this.state.userUID)
.orderBy("date_created", "desc");
}
componentDidMount() {
this.pullUserInfo()
this.unsubscribe = this.firestoreRef.onSnapshot(this.getCollection);
}
componentWillUnmount(){
this.unsubscribe();
}
getCollection = (querySnapshot) => {
const userPostsArray = [];
querySnapshot.forEach((res) => {
const {
...
} = res.data();
userPostsArray.push({
...
});
});
this.setState({
userPostsArray,
isLoading: false,
});
this.pullUserInfo()
}
pullUserInfo = async() => {
await Firebase.firestore()
.collection('users')
.doc(this.state.userUID)
.onSnapshot(function(doc){
if (doc.exists) {
console.log("pulling info")
this.setState({
postCount: doc.data().postCount,
followerCount: doc.data().followerCount,
followingCount: doc.data().followingCount,
storage_image_uri: doc.data().profilePic,
bio: doc.data().bio,
isLoading: false
})
} else {
console.log("No such document!");
}
}.bind(this))
}
gotToSettings() {
this.state.navigation.navigate('Settings')
}
//How I show my profile stats, follower, following, bio, profile pic
renderListHeader = () => {
return (
<View style = {styles.container}>
<View style={{ flexDirection: "row", padding: 20 }}>
<Text style = {styles.subheader}> {this.state.user.username} </Text>
</View>
<View style={{ flex: 1, flexDirection: "row", alignItems: 'center',
justifyContent: 'center',}}>
<ProfilePic storage_image_uri = {this.state.storage_image_uri} />
<ProfileStats postCount = {this.state.postCount} followerCount = {this.state.followerCount} followingCount = {this.state.followingCount}/>
</View>
<View style = {styles.lineStyle} />
<ProfileBio bio = {this.state.bio}/>
<TouchableOpacity
onPress={() => this.gotToSettings()}>
<Ionicons name="ios-settings" size={35} color="black" />
</TouchableOpacity>
</View>
)
}
render() {
const { navigation } = this.props;
const renderItem = ({ item }) => (
<CurrentUserPostCell
...
/>
);
if(this.state.isLoading){
return(
<View styles = {styles.container}>
<ActivityIndicator size="large" color="#9E9E9E"/>
</View>
)
}
return (
<View>
<FlatList
data={this.state.userPostsArray}
renderItem={renderItem}
keyExtractor={item => item.key}
ListHeaderComponent={this.renderListHeader}
contentContainerStyle={{ paddingBottom: 50 }}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
/>
</View>
)
}
}
As you can see, when the component mounts, pullUserInfo() is called, as well as the collection of user posts is fetched. There is no issue here getting this information for the first time.
My problem is, when I post something new, or follow someone new, the post count and following count goes up respectively in Firestore. But the new numbers are not reflected in my profile.
It is worth noting, when I create a new post, the new post is displayed in on the user profile. But even this event does not update pullUserInfo()
So, the information is not updating. Furthermore, when I click on the profile tab, I get the following warning:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method
How can I fix this, so that if the user information in Firestore changes, the changes update in the profile?
EDIT: I partly fixed my problem by adding "pull down to refresh" logic to the flatlist, since the flatlist header is the user profile information, and the flatlist data is the list of user posts.
At the end of flatlist in my render():
return (
<View>
<FlatList
data={this.state.userPostsArray}
renderItem={renderItem}
keyExtractor={item => item.key}
ListHeaderComponent={this.renderListHeader}
contentContainerStyle={{ paddingBottom: 50 }}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
onRefresh={this._refresh}
refreshing={this.state.isLoading}
/>
</View>
)
Outside the render:
_refresh = () => {
this.setState({ isLoading: true });
this.pullUserInfo()
};
This doesn't solve the full problem of the profile component only mounting once, therefore not refreshing the user stats automatically. The user has to manually pull down to refresh their profile if they want to see an updated bio, follower/following count, etc.
Related
I want to pass the item (asset) from a Flastlist and present in a child screen.
But when I press the item, the parameter is null. I have to go back and press it again for the parameter to be set. And if I press a different item, the old item still lingers until I press the new item a second time.
I don't know if useEffect is the best way to do it. I am just trying different approaches but have not had any luck with useEffect, useFocusEffect, or none.
Parent with the Flastlist
export default function SitesScreen(props) {
const [sites, setSites] = useState(["Power Plant", "Paper Mill", "Plastic Injection"])
const [selectedItem, setSelectedItem] = useState(null)
const Item = ({ item, onPress }) => (
<TouchableOpacity onPress={onPress} style={[styles.item]} >
<Text style={styles.text}>{item}</Text>
</TouchableOpacity>
)
const renderItem = ({ item }) => {
return (
<View style={styles.itemContainer} >
<Item
item={item}
onPress={() => onItemSelected(item)}
/>
</View >
)
}
const onItemSelected = (item) => {
setSelectedItem(item)
props.navigation.navigate("Asset", { asset: selectedItem })
}
return (
<View style={styles.container}>
<CustomHeader title="Sites" navigation={props.navigation} isHome={true} ></CustomHeader>
<View style={styles.contentContainer}>
<View style={{ width: '90%', height: '50%', alignItems: 'center', bottom: -150 }} >
<FlatList
data={sites}
renderItem={renderItem}
keyExtractor={(item) => JSON.stringify(item)}
/>
</View>
</View>
</View>
)}
Child screen to present item
export default function SitesScreen(props) {
const [asset, setAsset] = useState('')
useEffect(() => {
setAsset(props.route.params.asset)
console.log(asset)
}, [])
return (
<View style={styles.container}>
<CustomHeader title="Asset" navigation={props.navigation} isHome={false} ></CustomHeader>
<View style={styles.contentContainer}>
<Text style={styles.text} >{asset}</Text>
<View style={{ width: '90%', height: '50%', alignItems: 'center', bottom: -150 }} >
</View>
</View>
</View>
)}
When you pass the selectedItem value as a parameter to the next screen, the new state set from setSelectedItem has not been applied to the component yet. Because the new state hasn't been applied, you are still passing the initial null value that was set for selectedItem. This happens because state values are used by functions based on their current closures.
See this StackOverflow post for a more detailed explanation about this problem.
Problem solved.
The parent screen needs to have the navigate action in the useEffect hook, and not in the onItemSelected function. That way it waits until the state has been changed before it navigates.
useEffect(() => {
if (selectedItem) {
props.navigation.navigate("Asset", { asset: selectedItem })
}
}, [selectedItem])
I am struggling with useFocusEffect or useEffect. I am trying to fetch data when user navigate to the user profile this is working but when user navigate back to the profile i have a duplicate posts! I've been working on this problem for 3 days. Please any ideas.
This is the code:
import React, { useState, useEffect, useRef } from "react";
import {
View,
Text,
FlatList,
Image,
TouchableOpacity,
Animated,
ActivityIndicator,
} from "react-native";
import APIKIT from "../../../services/APIKit";
import styles from "./UserPostStyle";
import moment from "moment";
import { MaterialCommunityIcons, FontAwesome } from "#expo/vector-icons";
import { useNavigation } from "#react-navigation/native";
import { useFocusEffect } from "#react-navigation/native";
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
moment.updateLocale("en", {
relativeTime: {
future: "in %s",
past: "%s ago",
s: "Just now",
ss: "%ss",
m: "1m",
mm: "%dm",
h: "1h",
hh: "%dh",
d: "1d",
dd: "%dd",
M: "1mo",
MM: "%dmo",
y: "1y",
yy: "%dy",
},
});
export function UserPosts({ ...props }) {
const [userPosts, setUserPosts] = useState([]);
const [posts, setPosts] = useState([]); // Array with the posts objects
let postsArray = useRef(posts).current; // Auxiliar array for storing posts before updating the state. Pd: this will not re-render the component as it is a reference
const [page, setPage] = useState(1);
const [lastPage, setLastPage] = useState(0);
const [loadingMore, setLoadingMore] = useState(false);
const navigation = useNavigation();
useFocusEffect(
React.useCallback(() => {
if (props.userId) {
fetchPosts();
}
return () => {};
}, [props.userToken, props.userId, page])
);
// useEffect(() => {
// console.log(" -----------useEffect page", page);
// console.log(" -----------useEffect lastPage", lastPage);
// let mounted = true;
// if (mounted && props.userId) {
// fetchPosts();
// }
// return () => {
// console.log("return useEffect page", page);
// console.log("return useEffect lastPage", lastPage);
// mounted = false;
// };
// }, [props.userToken, props.userId, page]);
const fetchPosts = async () => {
if (props.userToken !== null) {
const config = {
headers: { Authorization: `Bearer ${props.userToken}` },
};
APIKIT.get(`/user/posts/${props.userId}?page=${page}`, config)
.then((response) => {
let posts = [];
const { data } = response;
setLastPage(data.userPosts.last_page);
for (let userPost of data.userPosts.data) {
let post = {};
post["id"] = userPost.id;
post["location_coordinate"] = userPost.location_coordinate;
post["location_icon"] = userPost.location_icon;
post["location_id"] = userPost.location_id;
post["location_title"] = userPost.location_title;
post["location_vicinity"] = userPost.location_vicinity;
post["post_body"] = userPost.post_body;
post["created_at"] = moment(userPost.created_at).fromNow(true);
post["user_id"] = userPost.user_id;
posts.push(post);
}
posts.forEach((newPost) => {
// Add the new fetched post to the head of the posts list
postsArray.unshift(newPost);
});
setPosts([...postsArray]); // Shallow copy of the posts array to force a FlatList re-render
setUserPosts((prevPosts) => [...prevPosts, ...posts]);
setLoadingMore(false);
if (page === lastPage) {
setLoadingMore(false);
return;
}
})
.catch((e) => {
console.log("There is an error eccured while getting the posts ", e);
setLoadingMore(false);
});
}
};
const Item = ({
post_body,
id,
location_coordinate,
location_icon,
location_title,
location_vicinity,
location_id,
created_at,
}) => (
<View style={styles.postContainer}>
<TouchableOpacity
onPress={() => {
navigation.navigate("PostDetailsScreen", {
location_coordinate: JSON.parse(location_coordinate),
userAvatar: props.userAvatar,
username: props.username,
name: props.name,
created_at: created_at,
post_body: post_body,
location_title: location_title,
location_vicinity: location_vicinity,
location_icon: location_icon,
location_id: location_id,
});
}}
>
<View style={styles.postHeader}>
<Image
style={styles.userAvatar}
source={
props.userAvatar
? { uri: props.userAvatar }
: require("../../../../assets/images/default.jpg")
}
/>
<View style={styles.postUserMeta}>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Text style={styles.name}>{props.name}</Text>
<Text style={styles.createdAt}>{created_at}</Text>
</View>
<Text style={styles.username}>#{props.username}</Text>
</View>
</View>
<View style={styles.postContent}>
<View>
<Text style={styles.postBody}>{post_body}</Text>
</View>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.locationInfoContainer}
onPress={() => {
navigation.navigate("PostPlaceDetailsScreen", {
location_coordinate: JSON.parse(location_coordinate),
userAvatar: props.userAvatar,
username: props.username,
name: props.name,
created_at: created_at,
post_body: post_body,
location_title: location_title,
location_vicinity: location_vicinity,
location_icon: location_icon,
location_id: location_id,
});
}}
>
<View style={styles.locationInfo}>
<Image style={styles.locationIcon} source={{ uri: location_icon }} />
<View style={styles.locationMeta}>
<Text style={styles.locationTitle}>{location_title}</Text>
</View>
</View>
</TouchableOpacity>
<View
style={{
borderWidth: 1,
borderColor: "#F2F2F2",
marginTop: 10,
marginBottom: 10,
}}
/>
<View style={styles.postFooter}>
<TouchableOpacity>
<MaterialCommunityIcons name="comment" size={24} color="#999999" />
</TouchableOpacity>
<TouchableOpacity>
<FontAwesome name="star" size={24} color="#999999" />
</TouchableOpacity>
{/* After fav color #FFBE5B */}
<TouchableOpacity>
<MaterialCommunityIcons
name="dots-horizontal"
size={24}
color="#999999"
/>
</TouchableOpacity>
</View>
</View>
);
const renderItem = ({ item }) => (
<Item
location_coordinate={item.location_coordinate}
post_body={item.post_body}
id={item.id}
location_id={item.location_id}
location_icon={item.location_icon}
location_title={item.location_title}
location_vicinity={item.location_vicinity}
created_at={item.created_at}
/>
);
const emptyPosts = () => {
return (
<View style={styles.noPostsMessageContainer}>
<View style={styles.messageContainer}>
<Image
style={styles.noPostMessageImage}
source={require("../../../../assets/images/Logo.png")}
/>
<Text style={styles.noPostMessageText}>No posts yet!</Text>
</View>
</View>
);
};
const handleLoadingMore = () => {
if (page === lastPage) {
return;
}
setPage(page + 1);
setLoadingMore(true);
};
return (
<View style={styles.userPostContainer}>
<AnimatedFlatList
showsVerticalScrollIndicator={false}
data={userPosts}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={{
paddingTop: 250,
paddingBottom: 100,
}}
scrollEventThrottle={16}
onScroll={props.scrolling}
ListEmptyComponent={emptyPosts}
onEndReachedThreshold={0.5}
onMomentumScrollEnd={() => handleLoadingMore()}
ListFooterComponent={
loadingMore && <ActivityIndicator size="large" animating />
}
/>
</View>
);
}
FlatList
return (
<View style={styles.userPostContainer}>
<AnimatedFlatList
showsVerticalScrollIndicator={false}
data={userPosts}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={{
paddingTop: 250,
paddingBottom: 100,
}}
scrollEventThrottle={16}
onScroll={props.scrolling}
ListEmptyComponent={emptyPosts}
onEndReachedThreshold={0.5}
onMomentumScrollEnd={() => handleLoadingMore()}
ListFooterComponent={
loadingMore && <ActivityIndicator size="large" animating />
}
/>
</View>
);
handleLoadingMore function
const handleLoadingMore = () => {
if (page === lastPage) {
return;
}
setPage(page + 1);
setLoadingMore(true);
};
I think the problom is when the user navigate back to the profile the state still the same and useFocusEffect fetching the same data again.
setUserPosts((prevPosts) => [...prevPosts, ...posts]);
Thanks for you help.
I have never used this hook from "react-navigation" but in the doc it says:
Sometimes we want to run side-effects when a screen is focused. A side
effect may involve things like adding an event listener, fetching
data, updating document title, etc. While this can be achieved using
focus and blur events, it's not very ergonomic.
So, every time you focus an specific route the code will run.
I am trying to fetch data when user navigate to the user profile this
is working but when user navigate back to the profile i have a
duplicate posts!
What you are doing is wrong because you are not paginating your fetches, I mean, you will need a "pointer" to the last post you fetched... with this you will avoid to fetch the data you already have. Also, the user experience will be pretty faster, as you will have lighter responses from your DB.
Anyways, I suggest you to run this code in the "useEffect" hook from React Native and try to implement a db listener or an inifinite scroll with pagination. This will not work when you focus the screen, but you will be able to fetch data every time the user refresh the FlatList, just like Instagram, Twitter, Netflix...
Take a look on this: https://aboutreact.com/react-native-flatlist-pagination-to-load-more-data-dynamically-infinite-list/
If you really need to fetch data when you focus the specific route, just implement a pagination (save in your component state an index to the last post you fetched).
UPDATE
Sorry, I didn't see that you was doing the pagination in your code. It might be a problem when you update the state of your component...
Try something like that:
const [posts, setPosts] = useState([]); // Array with the posts objects
let postsArray = useRef(posts).current; // Auxiliar array for storing posts before updating the state. Pd: this will not re-render the component as it is a reference
const loadMorePosts = () => {
// ... stuff
// const newPosts = Fetch posts
newPosts.forEach((newPost) => {
// Add the new fetched post to the head of the posts list
postsArray.unshift(newPost);
})
// The last post will be at the top of the list
setPosts([...postsArray]); // Shallow copy of the posts array to force a FlatList re-render
}
// setUserPosts((prevPosts) => [...prevPosts, ...posts]);
I need your help I'm using firebase for my app. I'm trying to get the users ID not the logged users no all users I have. I want to show their (uid) simply like in an alert for each user. Also, I'm showing them in a flatlist and when I set item.uid in an alert it shows (undefined). But, all the other data of the user is shown correctly. This what I did until now:
**
users.js
**
export default class usersList extends React.Component{
state = {
loading: false,
uid: '',
users: [],
items: []
};
componentDidMount() {
let itemsRef = f.database().ref('users').once('value').then(snapshot => {
var data = snapshot.val();
var items = Object.values(data);
this.setState({items});
console.log(snapshot.val())
});
}
renderItem({item}) {
return (
<View key={index} style={{width: '100%', overflow:'hidden', marginBottom: 5, justifyContent:'space-between', borderBottomWidth:1, borderColor: 'grey'}}>
<View>
<View style={{padding:5, width:'100%', flexDirection: 'row', justifyContent: 'space-between'}}>
<Text>{item.email}</Text>
</View>
</View>
</View>
)
}
render() {
return (
<View style={styles.container}>
<ScrollView>
{
this.state.items.length > 0
? <ItemComponent items={this.state.items} navigation={this.props.navigation} />
: <Text>No stores</Text>
}
</ScrollView>
</View>
);
}
}
//ItemComponent.js
export default class ItemComponent extends Component {
static propTypes = {
items: PropTypes.array.isRequired
};
render() {
return (
<View style={styles.itemsList}>
{this.props.items.map((item, index) => {
return (
<View key={index}>
<TouchableOpacity
onPress={ () => alert(item.uid)}>
<Text style={styles.itemtext}>{item.email}</Text>
</TouchableOpacity>
</View>
)
})}
</View>
);
}
}
firebase.database().ref('user').on('value', (datasnapshot) => {
this.setState({
_id: datasnapshot.key
});`
This solution worked for me
<Text style={styles.yourStyleHere}>UID: {auth.currentUser?.uid} </Text>
I have an issue that I don't quite understand.
I would like to display messages contained in an array using several flatlists. Then I will have to group them by date.
The messages actually correspond to a series of questions and answers from a chat where each message is recorded in an offline database (PouchDB is used). So I would like to display in an interface the questions that the user has answered, in short, I want to view the logs.
Here is the code:
const screen = Dimensions.get('screen');
const styles = StyleSheet.create({
logsView: {
backgroundColor: '#dddddd',
paddingLeft: 15,
paddingRight: 2,
height: '100%',
},
dateContainer: {
width: '75%',
padding: 1,
marginTop: 5,
},
dateContent: {
textAlign: 'center',
},
});
export default class ComponentPlanDetailsScreen
extends ComeoMeAbstractScreen<PropsType, StateType> {
static navigationOptions = {
title: µte('MyPlans'),
headerRight: (<View />),
};
constructor(props: PropsType) {
super(props);
this.IfeelMessagesBusiness = new IfeelMessagesBusiness();
this.state = {
currentDate: new Date(),
markedDate: moment(new Date()).format('YYYY-MM-DD'),
messages: [],
};
}
componentDidMount = () => {
// Get all messages from chat history
this.IfeelMessagesBusiness.getAllIfeelMessages().then((result: Object) => {
this.setState({ messages: result });
});
};
// Render each item of Flatlist
renderLogItem = ({ item }: Object) => {
console.log(`je passe renderlogitem ${JSON.stringify(item)}`);
return <LogItem message={item} />;
}
// Key for data in FlatList component
keyExtractor = (item: Object, index: number): string => index.toString();
render() {
const test = [
{
stringValue: 'Did you take some drugs ?',
isImage: false,
isQuestion: true,
questionNumber: 6,
author: {
id: 1,
avatar: 'http://image.noelshack.com/fichiers/2016/47/1480031586-1474755093-risitas721.png',
username: 'Dr Risitas',
},
loggedDateTime: '1552033946989',
},
];
const today = this.state.currentDate;
const day = moment(today).format('x');
return (
<View>
<Carousel
animate={false}
indicatorSize={10}
height={screen.height * 0.7
}
>
<View>
<ScrollView
style={styles.logsView}
>
<View>
{this.state.messages.map((ItemListByDate: Object): Array<Object> => {
console.log(`je passe array ${JSON.stringify([ItemListByDate])}`);
return (
<View key={ItemListByDate.loggedDateTime.toString()}>
<View style={styles.dateContainer}>
{ (parseInt(ItemListByDate.loggedDateTime, 10) + 172800000) <= parseInt(day.toString(), 10) ?
<Text style={styles.dateContent}>{moment(parseInt(ItemListByDate.loggedDateTime, 10)).format('DD-MM-YYYY')}</Text>
:
<Text style={styles.dateContent}>{moment(parseInt(ItemListByDate.loggedDateTime, 10)).fromNow()}</Text>
}
</View>
<View>
<FlatList
data={[ItemListByDate]}
renderItem={this.renderLogItem}
keyExtractor={this.keyExtractor}
ref={(ref: any) => { this.flatList = ref; }}
onContentSizeChange={(): any => this.flatList.scrollToEnd({ animated: true })}
onLayout={(): any => this.flatList.scrollToEnd({ animated: true })}
/>
</View>
</View>);
})
}
</View>
</ScrollView>
</View>
</Carousel>
</View>
);
}
}
The problem I don't understand is that the renderLogItem function to call the LogItem component is never called while the ItemListByDate array is displayed in the logs. No messages are displayed, I just have the grey background of the ScrollView component.
On the other hand, if I use the test array instead of this.state.messages with the map function, the message is displayed correctly in the interface and renderLogItem is called correctly.
I understand that when my component is called for the first time, the state is empty and the switch to the componentDidMount function will in my case trigger the update of the state and cause a re-render. This also causes the map function to call up and normally displays each message
Maybe it is due to a display problem, where the messages would be hidden because the initial state of the messages is empty ?
Thank you in advance for your help !
My first thought is that it has to do with the fact that this.IfeelMessagesBusiness.getAllIfeelMessages() is asynchronous. So the first time the render method is called, it tries to map òver undefined and it never updates.
Could you try doing a flatlist of Flatlist maybe ?
please help me out to implement pull to refresh on my app, I'm kinda new to react native, thanks. I don't know how to handle onRefresh and refreshing.
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>
)
}
return (
<Root>
<HomeHeader />
<FlatList
contentContainerStyle={{ alignSelf: 'stretch' }}
data={data.getPosts}
keyExtractor={item => item._id}
renderItem={this._renderItem}
numColumns={3}
refreshing={this.state.refreshing}
onRefresh={this._handleRefresh}
/>
</Root>
);
}
}
export default graphql(GET_POSTS_QUERY)(HomeScreen);
Set variable
this.state = {
isFetching: false,
}
Create Refresh function
onRefresh() {
this.setState({isFetching: true,},() => {this.getApiData();});
}
And in last set onRefresh and refreshing in FlatList.
<FlatList
data={ this.state.FlatListItems }
onRefresh={() => this.onRefresh()}
refreshing={this.state.isFetching}
/>
After fetching Data in function getApiData Make sure to set false isFetching.
this.setState({ isFetching: false })
You can also use refreshControl in Flatlist, ScrollView, and any other list component.
<FlatList
contentContainerStyle={{ alignSelf: 'stretch' }}
data={data.getPosts}
keyExtractor={(item) => item._id}
renderItem={this._renderItem}
numColumns={3}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._handleRefresh}
/>
}
/>
If someone is interested in doing it with React Hooks here is it:
First create isRefreshing state:
const [isRefreshing, setIsRefreshing] = useState(false)
Then create your onRefresh function that calls API to refresh data:
const onRefresh = () => {
//set isRefreshing to true
setIsRefreshing(true)
callApiMethod()
// and set isRefreshing to false at the end of your callApiMethod()
}
And after that, your FlatList component that looks like that:
return (
<FlatList
style={styles.flatList}
onRefresh={onRefresh}
refreshing={isRefreshing}
data={data}
renderItem={renderItem}
keyExtractor={item => item.id.toString()}
/>
)
// Sample code representing pull to refresh in flatlist. Modify accordingly.
import React, { Component } from 'react'
import { Text, View,StyleSheet,FlatList,Dimensions,Image,TouchableHighlight } from 'react-native'
export default class Home extends Component {
constructor(props)
{
super(props);
this.state = {
data : [],
gender : "",
isFetching: false,
}
}
componentWillMount()
{
this.searchRandomUser()
}
onRefresh() {
this.setState({ isFetching: true }, function() { this.searchRandomUser() });
}
searchRandomUser = async () =>
{
const RandomAPI = await fetch('https://randomuser.me/api/?results=20')
const APIValue = await RandomAPI.json();
const APIResults = APIValue.results
console.log(APIResults[0].email);
this.setState({
data:APIResults,
isFetching: false
})
}
render() {
return (
<View style = {styles.container}>
<TouchableHighlight
onPressOut = {this.searchRandomUser}
style = {{width:deviceWidth - 32, height:45,backgroundColor: 'green',justifyContent:"center",alignContent:"center"}}>
<Text style = {{fontSize:22,color: 'white',fontWeight: 'bold',textAlign:"center"}}>Reload Data</Text>
</TouchableHighlight>
<FlatList
data={this.state.data}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isFetching}
keyExtractor = { (item, index) => index.toString() }
renderItem={({item}) =>
<View style = {styles.ContainerView}>
<View>
<Image
source={{uri : item.picture.large}}
style = {{height:100,width:100,borderRadius: 50,marginLeft: 4}}
resizeMode='contain'
/>
</View>
<View style = {{flexDirection: 'column',marginLeft:16,marginRight: 16,flexWrap:'wrap',alignSelf:"center",width: deviceWidth-160}}>
<Text>Email Id : {item.email}</Text>
<Text>Full Name : {this.state.gender} {item.name.first} {item.name.last}</Text>
<Text>Date of birth : {item.dob.age}</Text>
<Text>Phone number : {item.phone}</Text>
</View>
</View>
}
/>
</View>
)
}
}
const deviceWidth = Dimensions.get('window').width
const deviceHeight = Dimensions.get('window').height
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
marginTop:22
},
ContainerView:
{
// backgroundColor:'grey',
marginBottom:20,
paddingVertical:10,
backgroundColor: '#F5F5F5',
borderBottomWidth:0.5,
borderBottomColor:'grey',
width: deviceWidth-40,
marginLeft:20,
marginRight:20,
marginTop:20,
flexDirection:'row'
}
});
const [refreshing, setRefreshing] = useState(false)
<FlatList
style={{ padding: 15 }}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={_onRefresh}
tintColor="#F8852D"/>
}
showsVerticalScrollIndicator={false}
data={listData}
renderItem={renderItem}
item={(item, index) => index.toStirng()}
onEndReachedThreshold={0.8}
onEndReached={() => onEndReached()}
ListFooterComponent={() => renderFooter()}
/>
_onRefresh Function
const _onRefresh = () => {
console.log('_onRefresh')
setRefreshing(true);
getPosts();
};
"Pull to refresh" concept implies that the list can be manually refreshed thus can be changed outside the current view (e.g. fetched from server). So the callback onRefresh has to trigger the data reloading process (e.g. send a request to the server) and set the refreshing variable to truthy value. This will notify the user that the process was started by showing loading indicator. Once you got the data ready you need to set refreshing to falsy so the view will hide loading indicator.
this is the best that I can do. my Code Image
when I pull it down it dosen't refetch data from server I'm running graphql server which connected by Apollo to the app, and I don't know how to get data from server in _getData() function.
Instead of using pull to refresh on flat-list simply use on Scroll View.
Set Refresh State
this.state = {
isFetching: false,
}
Set onFresh in ScrollView
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.refresh}
onRefresh={() => this.onRefresh()}
tintColor="red"
/>
}>
<FlatList data={data} renderItem={this._renderItem} numColumns={3} />
</ScrollView>;
In OnRefreshFunction
onRefresh() {
this.setState({refresh: true});
setTimeout(() => {
this.setState({refresh: false});
}, 2000);
}
You can check the official document:
https://reactnative.dev/docs/refreshcontrol
For anyone looking for an updated answer using new React Hooks.
You can use isLoading and refetch from useQuery.
Then use RefreshControl with FlatList. Read more on RefreshControl: https://reactnative.dev/docs/refreshcontrol
const RenderImageGrid = (item) => <ImageGrid item={item} />
export const HomeScreen = () => {
const { data, isLoading, refetch } = useQuery('get_home_screen_data', GET_HOME_SCREEN_DATA)
return (
{isLoading ? (
<Root>
<Loading size="large" />
</Root>
) : (
<Root>
<HomeHeader />
<FlatList
data={data}
keyExtractor={item => item._id}
renderItem={RenderImageGrid}
numColumns={3}
refreshControl={
<RefreshControl
refreshing={isLoading}
onRefresh={refetch}
/>
}
/>
</Root>
)}
)
}
export default HomeScreen