Fetch document only once when Firestore has updated - arrays

I have a tableView that bring all documents from Firestore collection and i want to fetch the last document only once when it is added to the Firestore after user refresh the tableView and after that I want to remove the listener so when the user refresh the tableView it only fetch document once , i used detach listener [from the documentation here][1] but it didn't work
func updatedFireStoredata() {
let listener = Firestore.firestore().collection("collection1").order(by: "date").limit(toLast: 1).addSnapshotListener() { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let data = diff.document.data()
self.amount.append(data["amount"] as? String ?? "")
print("New city: \(diff.document.data())")
}
}
}
table2.reloadData()
listener.remove()
}

If your goal is to only call once to retrieve this document, I would consider using the offline persistence that is built in to the SDK. You will need to request the document once with offline persistence enabled, but after that call you can request this data from that local cache.
https://firebase.google.com/docs/firestore/manage-data/enable-offline
https://firebase.google.com/docs/firestore/manage-data/enable-offline#disable_and_enable_network_access
Alternatively you could store the document locally, and not include it in reload.
If it is the case that this data does change, just not as often as every refresh, it may make sense to move this to an eventing model. You can use firestore watch/listen to monitor that query and get update callbacks as needed. https://firebase.google.com/docs/firestore/query-data/listen

There are a couple of things
First, requiring a user to keep clicking refresh to get the current data is not a good experience. The data should be presented to the user automatically without interaction
Second, Firebase is asynchronous - code outside the Firebase closure will execute before the data in the closure. See my answer Firebase is asynchronous <- that's for the Realtime Database but the concept applies to asynchronous functions in general.
To fix both issues here's updated code
func updatedFireStoredata() {
let listener = Firestore.firestore().collection("collection1").order(by: "date").limit(toLast: 1).addSnapshotListener() { querySnapshot, error in
if let err = error {
print("Error fetching snapshots: \(err.localizedDescription)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let data = diff.document.data()
self.amount.append(data["amount"] as? String ?? "")
print("New city: \(diff.document.data())")
}
}
table2.reloadData() //needs to be inside the closure
}
}
I moved the tableView reload inside the closure so it executes when new data arrives and then removed the code to remove the listener so your tableView will update with new data.
I also updated the error handler to handle an actual error code.
One thing you will want to do is to keep track of what the user has selected is is looking at - you don't want a selected item, for example, to un-select if other data is added which is what will happen when the tableView reloads.

Related

How to force ngrx-data to clear cashed entities and reload data from db

I have a typical ngrx-data arrangement of 'User' entities linked to db.
I implement the standard service to handle the data:
#Injectable({providedIn: 'root'})
export class UserService extends EntityCollectionServiceBase<UserEntity> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('User', serviceElementsFactory);
}
}
I read the data using:
this.data$ = this.userService.getAll();
this.data$.subscribe(d => { this.data = d; ... }
Data arrives fine. Now, I have a GUI / HTML form where user can make changes and update them. It also works fine. Any changes user makes in the form are updated via:
this.data[fieldName] = newValue;
This updates the data and ngrx-data automatically updates the entity cache.
I want to implement an option, where user can decide to cancel all changes before they are written to the db, and get the initial data before he made any adjustments. However, I am somehow unable to overwrite the cached changes.
I tried:
this.userService.clearCache();
this.userService.load();
also tried to re-call:
this.data$ = this.userService.getAll();
but I am constantly getting the data from the cache that has been changed by the user, not the data from the db. In the db I see the data not modified. No steps were taken to write the data to db.
I am not able to find the approach to discard my entity cache and reload the original db data to replace the cached values.
Any input is appreciated.
You will need to subscribe to the reassigned observable when you change this.data$, but it will be a bit messy.
First you bind this.data$ via this.data$ = this.userService.entities$, then no matter you use load() or getAll(), as long as the entities$ changed, it fire to this.data$.subscribe(). You can even skip the load and getAll if you already did that in other process.
You can then use the clearCache() then load() to reset the cache.
But I strongly recommand you to keep the entity data pure. If the user exit in the middle without save or reset, the data is changed everywhere you use this entity.
My recommand alternatives:
(1) Use angular FormGroup to set the form data with entity data value, then use this setting function to reset the form.
(2) Make a function to copy the data, then use this copy function as reset.
For example using _.cloneDeep.
(2.1) Using rxjs BehaviourSubject:
resetTrigger$ = new BehaviourSubject<boolean>(false);
ngOnInit(){
this.data$ = combineLastest([
this.resetTrigger$,
this.userService.entities$
]).subscribe([trigger, data])=>{
this.data = _.cloneDeep(data)
});
// can skip if already loaded before
this.userService.load();
}
When you want to reset the data, set a new value to the trigger
resetForm(){
this.resetTrigger$.next(!this.resetTrigger$.value)
}
(2.2) Using native function (need to store the original):
this.data$ = this.userService.entities$.pipe(
tap(d=>{
this.originData = d;
resetForm();
})
).subscribe()
resetForm:
resetForm:()=>{
this.data = _.cloneDeep(this.originData);
}

How to Update an array without downloading all data from firebase ReactJs

Actually am new in react and am trying to create an event app in which a user can join an event
here is code for joining an event
export const JoinEvent = (id) => {
return async dispatch => {
let data = await firebase.firestore().collection('Events').doc(id).get()
let tmpArray = data.data()
let currentUser = firebase.auth().currentUser
let newArray = tmpArray.PeopleAttending
await firebase.firestore().collection('Events').doc(id).update({
PeopleAttending : {...newArray, [currentUser.uid]: {displayName : currentUser.displayName}}
})
}
}
actually i have created an action bascailly in JoinEvent an id is passed of the particular event which is clicked.
here is my firestore structure look like this..
so basically i have to download the whole data and store in local array and then add new user and then finally update
So here am basically download the whole data is there any way to just simply add new Object without downloading whole data??
thankyou
You are doing it wrong. Firestore document size limit is Maximum size for a document 1 MiB (1,048,576 bytes), so sooner or later you're going to reach that limit if you keep adding data like this. It may seems that you're not going to reach that limit, but it's very unsafe to store data that way. You can check Firestore query using an object element as parameter how to query objects in firestore documents, but I suggest you don't do it that way.
The proper way to do it, is to create a subcollection PeopleAttending on each document inside the Events collection and then use that collection to store the data.
Also you can try document set with merge or mergeFields like documented here https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html#set and here https://stackoverflow.com/a/46600599/1889685.

JSON stored into an array not callable by specific index

I am trying to develop an app for my fantasy baseball league to use for our draft (we some kind of quirky stuff all the major sites don't account for) - I want to pull some player data to use for the app by using MLB's API. I have been able to get the response from MLB, but can't do anything with the data after I get it back. I am trying to store the JSON into an array, and if I console.log the array as a whole, it will give me the entire chunk of data, but if I try to call the specific index value of the 1st item, it comes back as undefined.
let lastName = 'judge';
let getData = new XMLHttpRequest;
let jsonData = [];
function getPlayer () {
getData.open('GET', `http://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, true)
getData.onload = function() {
if (this.status === 200) {
jsonData.push(JSON.parse(this.responseText));
}
}
getData.send();
console.log(jsonData);
}
When I change the above console.log to console.log(jsonData[0]) it comes back as undefined. If I go to the console and copy the property path, it displays as [""0""] - Either there has to be a better way to use the JSON data or storing it into an array is doing something abnormal that I haven't encountered before.
Thanks!
The jsonData array will be empty after calling getPlayer function because XHR loads data asynchronously.
You need to access the data in onload handler like this (also changed URL to HTTPS to avoid protocol mismatch errors in console):
let lastName = 'judge';
let getData = new XMLHttpRequest;
let jsonData = [];
function getPlayer () {
getData.open('GET', `https://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, true)
getData.onload = function() {
if (this.status === 200) {
jsonData.push(JSON.parse(this.responseText));
// Now that we have the data...
console.log(jsonData[0]);
}
}
getData.send();
}
First answer from How to force a program to wait until an HTTP request is finished in JavaScript? question:
There is a 3rd parameter to XmlHttpRequest's open(), which aims to
indicate that you want the request to by asynchronous (and so handle
the response through an onreadystatechange handler).
So if you want it to be synchronous (i.e. wait for the answer), just
specify false for this 3rd argument.
So, you need to change last parameter in open function as below:
getData.open('GET', `http://lookup-service-
prod.mlb.com/json/named.search_player_all.bam?
sport_code='mlb'&active_sw='Y'&name_part='${lastName}%25'`, false)
But from other side, you should allow this method to act asynchronously and print response directly in onload function.

After axios call, how should I update the front-end? [I have 2 ways]

I have an array of todos. When I delete one of them, I succesfully delete it also from the database with DELETE call. However, I am not sure how to update the front-end. First way is changing the state by deleting the related todo.
onDelete(todo) {
axios.delete('api/todos/' + todo.id).then(res => {
var array = [...this.state.todos]; // make a separate copy of the array
var index = array.indexOf(todo)
array.splice(index, 1);
this.setState({todos: array}); // other state elemenets other than todos will not be affected
});
}
Other way is, making a new axios GET request to get all the todos from the database. (this will be an axios request inside an axios request)
onDelete(todo) {
axios.delete('api/todos/' + todo.id).then(res => {
// make an axios get request on api/todos
// then, set state with data in response.
});
}
Hence, which one is the better approach?
The best approach would be the second one, to call a get request once more. So that no matter what the change database has undergone, the UI will be an exact replica of the data from db instead of manually changing(deleting in this case) the list from UI. However there will be the delay of api response for UI updation in this case.

My flux store gets re-instantiated on reload

Okay. I'm kinda new to react and I'm having a #1 mayor issue. Can't really find any solution out there.
I've built an app that renders a list of objects. The list comes from my mock API for now. The list of objects is stored inside a store. The store action to fetch the objects is done by the components.
My issue is when showing these objects. When a user clicks show, it renders a page with details on the object. Store-wise this means firing a getSpecific function that retrieves the object, from the store, based on an ID.
This is all fine, the store still has the objects. Until I reload the page. That is when the store gets wiped, a new instance is created (this is my guess). The store is now empty, and getting that specific object is now impossible (in my current implementation).
So, I read somewhere that this is by design. Is the solutions to:
Save the store in local storage, to keep the data?
Make the API call again and get all the objects once again?
And in case 2, when/where is this supposed to happen?
How should a store make sure it always has the expected data?
Any hints?
Some if the implementation:
//List.js
componentDidMount() {
//The fetch offers function will trigger a change event
//which will trigger the listener in componentWillMount
OfferActions.fetchOffers();
}
componentWillMount() {
//Listen for changes in the store
offerStore.addChangeListener(this.retriveOffers);
}
retrieveOffers() {
this.setState({
offers: offerStore.getAll()
});
}
.
//OfferActions.js
fetchOffers(){
let url = 'http://localhost:3001/offers';
axios.get(url).then(function (data) {
dispatch({
actionType: OfferConstants.RECIVE_OFFERS,
payload: data.data
});
});
}
.
//OfferStore.js
var _offers = [];
receiveOffers(payload) {
_offers = payload || [];
this.emitChange();
}
handleActions(action) {
switch (action.actionType) {
case OfferConstants.RECIVE_OFFERS:
{
this.receiveOffers(action.payload);
}
}
}
getAll() {
return _offers;
}
getOffer(requested_id) {
var result = this.getAll().filter(function (offer) {
return offer.id == requested_id;
});
}
.
//Show.js
componentWillMount() {
this.state = {
offer: offerStore.getOffer(this.props.params.id)
};
}
That is correct, redux stores, like any other javascript objects, do not survive a refresh. During a refresh you are resetting the memory of the browser window.
Both of your approaches would work, however I would suggest the following:
Save to local storage only information that is semi persistent such as authentication token, user first name/last name, ui settings, etc.
During app start (or component load), load any auxiliary information such as sales figures, message feeds, and offers. This information generally changes quickly and it makes little sense to cache it in local storage.
For 1. you can utilize the redux-persist middleware. It let's you save to and retrieve from your browser's local storage during app start. (This is just one of many ways to accomplish this).
For 2. your approach makes sense. Load the required data on componentWillMount asynchronously.
Furthermore, regarding being "up-to-date" with data: this entirely depends on your application needs. A few ideas to help you get started exploring your problem domain:
With each request to get offers, also send or save a time stamp. Have the application decide when a time stamp is "too old" and request again.
Implement real time communication, for example socket.io which pushes the data to the client instead of the client requesting it.
Request the data at an interval suitable to your application. You could pass along the last time you requested the information and the server could decide if there is new data available or return an empty response in which case you display the existing data.

Resources