I am fetching Data from an API to a flatlist and its working fine the problem it's not a real time fetching, I have to refresh the screen in order to see the recent added data. How can I make the fetching real time.
export default class CategoryScreen extends Component {
constructor(props) {
super(props);
this.state = {
categories: []
}
}
componentWillMount() {
//Fetch recent products
const url = url
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
categories: responseJson.categories
})
})
}
rederItem = ({ item }) => {
return (
<CategoryFlatList title={item.cat_name} image={item.img}/>
)
}
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<FlatList style={{}}
data={this.state.categories}
renderItem={this.rederItem}
numColumns={2}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
}
If I understand your problem correctly you need to refresh the data when it was changed on the server!
If this is the case, the reason why it did not update on the list cause you did not call fetch, when u refresh the screen the fetch get called and the data get updated.
There are multiple solutions to fix this problem:
An advanced one by creating a mechanism to subscribe to the data source and update when it is changed.
Update data on user interaction (like a swipe).
A simpler way (but not that professional) by using a timer that calls the fetch and updates the state.
These are the things you can do:
1) Make a PubSub mechanism
2) Use pull to refresh in your FlatList
3) Use polling, you can either do polling by using Redux-Sagas or Redux-Observable which is the preferred method or you can do it by using timer like this
This is how you start interval
this.intervalID = setInterval(() => {
// do something
}, 5000) //time for polling in millies
This is how you can stop interval
if (this.intervalID != null) {
clearInterval(this.intervalID)
this.intervalID = null
}
Related
I have a pretty simple messaging component, all of the mutations and queries involved work properly. If I send a message, the mutation fires, and then the updated conversation is pulled from a query and changes the local state value of currentChat to the newest version of that chat. The query works, as shown by console.log statements, but the screen does not re-render or update at all. If I leave the page and come back to it, it is now updated. Similarly, if I hit ctrl-s in my IDE, the page refreshed and the expected messages were displayed. I use useEffects to mark a change in currentChatroom and all of the rendering functions are based off of values in that state, so I am unsure why it is not updating the visuals when the data has been updated
EDIT: Adding some code for clarity, the functions are all pretty huge (mostly styling logic) so I'm gonna show the barebones of it.
const [msgs, setMsgs] = useRecoilState(messageDataState)
// RENDERINGS //
// Renders the Next Upcoming Meetings
function renderUpcomingMeetings(){
if (msgs.length < 1){ // no meetings
return(
<View style={Styles.upcomingView}>
<Text style={{...FONTS.Title, color: COLORS.iconLight, textAlign: 'center', margin: 10}}>
You have no messages yet!
</Text>
</View>
)
}
return(
<ScrollView style={{...Styles.upcomingView, maxHeight: 350}}>
{renderMsgs()}
</ScrollView>
)
}
// Renders each meeting card
function renderMsgs(){
return msgs.filter(msg => {
if (!msg.sent && msg.deleted){
return null
}
}).map(meeting => {
let dt = convertDateTimeToJavaScript(msg.sentTime)
return(
// A bunch of irrelevant styling stuff
)
})
}
// SENDINGS //
// Sends message
async function handleAssignSubmissionClick(){
setLoading(true)
sendMessage({client: client, sDate: sDate})
.then((resolved) => {
setRefresh(!refresh)
})
}
// Triggers the Requery when refresh is changed post mutation
useEffect(() => {
getAndSetUser()
setLoading(false)
}, [refresh])
// Gets the user obj and resets the userState
async function getAndSetUser(){
await client.query({
query: GET_CHAT,
fetchPolicy: 'network-only'
})
.then(async (resolved) => {
await setMsgs(resolved.data.getChat.messages)
})
.catch((error) => {
console.log(error)
})
}
How do I filter my componentDidMount data without it throwing an error, filter is not supported in componentDidMount
this is my code currently :
componentDidMount() { //fetch data from json server
fetch("http://0.0.0.0:8001/shopData")
.then(response => response.json())
.then(data => {
//console.log(data)
item = data.filter(item =>{
item.id === shopid
})
this.setState({item :data})
console.log(itemArray)
})
.catch(err => {
console.log("Error loading data" + err);
});
}
render(){
item =this.props.navigation.getParam('item');
shopid= item.shop_id;
console.log(this.state.shopData)
return(
<View>
<Text>The title </Text>
<Text>{shopid}</Text>
<Text>{item.name} </Text>
</View>
}
When I run my code, it just gives an empty array. I need to component did mount and retrieve it from this server :fetch("http://0.0.0.0:8001/shopData") so changing it to a static file is not an option. Can anyone provide insight on why my filter isn't working? thanks. For shopid, I retrieve it from a previous screen using this.props in the render section but I believe it should still be able to pass the value to componentDidMount because I am able to log the value gotten
To filter an object, for each element you have to return either true or false. I see that you probably missed that point in here:
item = data.filter(item =>{
// You have to return boolean, else it always returns undefined
// which is assumed as false, gives you an empty list
return item.id === shopid
})
or
//if you use arrow without curly braces it's going to return the value
item = data.filter(item => item.id === shopid)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I have a problem how to pass user data after logging in via the 'profile' screen BUT it is not switched to the profile screen?
I only know the code below is the data transfer is complete then the screen will switch? I searched a lot on the internet but there was none or maybe I don't know how to search for this keyword.
this.props.navigation.navigate('Profile', {
data: this.state.data
});
You can use Async Storage to access data after switching navigators.
Also, I'm not sure what's the problem with passing data with navigation.navigate. You can use getParam() to get data on the next screen.
Update
Send data:
this.props.navigation.navigate('Profile', {
nickName: 'MohamadKh75'
});
Get data in Profile screen:
const name = this.props.navigation.getParam('nickName', 'defaultValue'); // name will be 'MohamadKh75'
const age = this.props.navigation.getParam('age', 123); // as we didn't pass 'age', the value is 123
Check here for more information!
If you don't want to switch screens but want to pass data across screens, first, since you don't want to switch screens, you don't need this:
this.props.navigation.navigate('Profile', {
data: this.state.data
});
And instead, you need this:
function_name() {
// some code we will discuss below...
}
Second, since you want the data available across screens or more specifically send the data across screens but not go to that screens, you can use local storage like Async Storage (or SQL lite for big projects). That way, the data is available between screens, but not go to that screens.
For example, in Async Storage, you can store data in local but not switch screens:
async storeData(key, value) {
try {
await AsyncStorage.setItem(key, value);
}
}
async button(key, value) {
await this.storeData(key, value);
// In case you like to switch screens anywhere you want,
// you can uncomment the code below:
// this.props.navigation.navigate('you screens')
}
render() {
return(
<Button onPress={() => this.button("data", this.state.data) } />
);
}
Then, you can retrieve it anywhere you want on your screens, whether you want to switch screens or not:
async getData(key) {
try {
const value = await AsyncStorage.getItem(key);
if (value !== null) {
return value;
}
}
}
async button(key) {
const data = await this.getData(key);
}
render() {
return(
<Button onPress={() => this.button("data") } />
);
}
I want to send my data from textinputs from an array, the reason why I choose array is because the api which I am calling is basically an array, all I want to do is to set those values from textinput to api and show response to next screen.
Here's my screen One which is taking these values and sending it to api, but problem is it is not sending value neither showing me response onto next screen:
constructor(props){
super(props);
this.state={
ze:[userid="",
ID="",
titlex="",
switchValue=false,]
}
}
handleSubmit(){
fetch("https://jsonplaceholder.typicode.com/todos/1", {
method: "POST",
// headers: headers,
body: JSON.stringify({
userId:this.state.ze.userid,
id:this.state.ze.ID,
title:this.state.ze.titlex,
completed:this.state.ze.switchValue,
})
})
.then(function(response){
return response.json();
})
.then(function(z){
console.log(z)
});
this.props.navigation.navigate('Pg',{value:this.state.ze});
}
This is my next screen on which it will show me response this is what I've done so far and I dont know how to show response from an array to next screen:
componentDidMount = () => {
const item = JSON.stringify(this.props.navigation.getParam('value'));
Alert.alert(item.id);
}
render() {
return (
<View>
<Text>
{/* Name:{this.state.} */}
</Text>
</View>
)
}
You are using
this.state={
ze:[userid="",
ID="",
titlex="",
switchValue=false,]
}
Which is in the form of Array, So you have to access it like
item[index].id
or use the
array.map((item, index){
return (
item.ID
)
})
You're sending it correctly but you're not reading it correctly.
Instead of this:
const item = JSON.stringify(this.props.navigation.getParam('value'));
Simply do this:
const item = this.props.navigation.getParam('value');
now to quickly display it, you can use something like this in your JSX:
<Text>{JSON.stringify(item)}</Text>
For something more user-friendly, each item on its own line, you can try something like this in your JSX:
{!!item.length && item.map(i => <Text>i</Text>)}
Of course, it depends on what type of items your array contains. Just adjust the code above.
I am implementing an app in React-Native where I am fetching "restaurants" as documents from Cloud-Firestore and I am also using onSnapshot() listener. When the app is ready for launch, there will probably be around max 3000 restaurants. I have a few questions to ask around this matter.
-Do I need to implement Pagination/Lazy Loading for a better UX and less cost OR 3000 is not a big number so it won't affect the performance that much?!
-If I do need to implement one of them, which one should I implement?
-If I dont need, then is there a way to compress the JSON data when fetching in React-Native so it saves space? And then decompress when requested by the user.
Can user search for a restaurant while lazy loading is implemented?
EDIT:
I managed to implement lazy-loading and its working perfectly, however, using snapshot() listener will make lazy-loading pointless but I must use it because I need to fetch on real-time new "restaurants" or "orders. So, what else can I use instead of snapshot()? Or maybe is there a way to still use snapshot() but with a small change to the code?
Second question: after the above problem is solved, am I able to implement the search for a restaurant? It seems quite tricky knowing that I am using lazy-loading.
componentDidMount() {
try {
// Cloud Firestore: Initial Query
this.retrieveData();
}
catch (error) {
console.log(error);
}
};
retrieveData = () => {
try {
this.setState({
loading: true
})
var initialQuery = firebase.firestore().collection('restaurants')
.orderBy('res_id')
.limit(this.state.limit)
//let documentSnapshots = await initialQuery.get();
initialQuery.onSnapshot((documentSnapshots => {
var All = documentSnapshots.docs.map(document => document.data());
var lastVisible = All[All.length - 1].res_id;
this.setState({
All: All,
lastVisible: lastVisible,
loading: false,
});
}));
}
catch (error) {
console.log(error);
}
};
retrieveMore = async () => {
try {
// Set State: Refreshing
this.setState({
refreshing: true,
});
// Cloud Firestore: Query (Additional Query)
var additionalQuery = await firebase.firestore().collection('restaurants')
.orderBy('res_id')
.startAfter(this.state.lastVisible)
.limit(this.state.limit)
// Cloud Firestore: Query Snapshot
var documentSnapshots = await additionalQuery.get();
// Cloud Firestore: Document Data
var All = documentSnapshots.docs.map(document => document.data());
// Cloud Firestore: Last Visible Document (Document ID To Start From For Proceeding Queries)
var lastVisible = All[All.length - 1].res_id;
// Set State
this.setState({
All: [...this.state.All, ...All],
lastVisible: lastVisible,
refreshing: false,
});
console.log('Retrieving additional Data', this.state.All);
}
catch (error) {
console.log(error);
}
};
// Render Header
renderHeader = () => {
try {
return (
<Text style={styles.headerText}>Items</Text>
)
}
catch (error) {
console.log(error);
}
};
// Render Footer
renderFooter = () => {
try {
// Check If Loading
if (this.state.loading) {
return (
<ActivityIndicator />
)
}
else {
return null;
}
}
catch (error) {
console.log(error);
}
};
render() {
return (
<SafeAreaView style={styles.container}>
<FlatList
// Data
data={this.state.All}
// Render Items
renderItem={({ item }) => (
<View style={styles.itemContainer}>
<Text>(ID: {item.res_id}) {item.rest_name} {item.rest_location}</Text>
</View>
)}
// Item Key
keyExtractor={(item, index) => String(index)}
// Header (Title)
ListHeaderComponent={this.renderHeader}
// Footer (Activity Indicator)
ListFooterComponent={this.renderFooter}
// On End Reached (Takes a function)
onEndReached={this.retrieveMore}
// How Close To The End Of List Until Next Data Request Is Made
onEndReachedThreshold={0.1}
// Refreshing (Set To True When End Reached)
refreshing={this.state.refreshing}
/>
</SafeAreaView>
)
}
Pagination and Lazy Loading are kind of the same thing in this case. You will be either switching pages or infinitely scrolling while fetching new data until there is no more data to fetch. In either case, you need it. Look here https://firebase.google.com/docs/firestore/query-data/query-cursors
As pagination is merely a way you query your data, it has no effect on how you can use this data in other ways, meaning sure you can search for a restaurant given your database design and security rules are set up right
Not sure if you should be using onSnapshot listener though, as it will return you entire collection every time something changes, which denies the whole point of using pagination
EDIT:
About real time updates: it depends on what do you want to fetch in real time. Is it only those restaurants you have loaded or all of them? Imagine you had 1.000.000 restaurants in your db, you sure wouldn't want to query for them all for real time updates. Refreshing only those that are currently loaded is pretty expensive operation in firestore as it requires canceling and creating new subscriptions every time your visible restaurants change (e.g. as you scroll through the page). So both variants are not an option
You should reconsider your app design - e.g. do you really need to live track orders of every restaurant registered? Maybe you only want those where you are an employee, or only those near you? Maybe you should restructure your database to keep orders separately from restaurants, that way you can listen for orders while still lazy loading restaurants?