Why is my Firestore documents' array field always only updated with one less item than the array I send from the front end? - javascript

I am trying to update a Firestore document once I save files to Google Cloud Storage. I want to send an array of URLs of the stored files to a document in Firestore as follows...
(attachments: [ {url: "fileone.jpeg", ...}, {url: "filetwo.jpeg", ...}, ...]).
This array of URLs are created at the front end by using firebases getDownloadURL(). I can successfully update my document but the array in the updated document always holds one less item than the array created at the front end. When console.log-ing the array stored in memory (to be sent to firestore) I see that the value within parentheses that preceeds the [{},{}] is one less than array.length as well.
What console logging the fileUrls array (stored in memory to be sent to firestore) shows even though fileUrls.length === 3 and expanding this below line shows all three URL containing objects starting at index 0 :
fileUrls: > (2) [{…}, {…}]
Here is the code from implementing this update process:
let fileUrls = []; // <<-- Array for file urls to be stored
let promises = [];
for (const file of state.post.files) {
const fileRef = storage.ref().child(`my file path`);
const p = fileRef.put(file)
.then(snapshot => {
fileRef.getDownloadURL()
.then(url => {
fileUrls.push({ url: url, type: file.type }) // Adding urls to arr
})
})
promises.push(p);
}
const all = Promise.all(promises);
all
.then(() => {
submissionRef.update({ // <<<--- Updating document created prior.
attachments: fileUrls
})
})
.catch(err => console.log(err));

You are waiting for the file to finish uploading, but you're not waiting until the download URL is fetched. You'll need to involve the promise returned by getDownloadURL into the array of promises passed to Promise.all(). Try returning its promise from the then callback for your file upload:
const p = fileRef.put(file)
.then(snapshot => {
// add a return here to chain the promise returned by getDownloadURL()
// with the promise returned by put()
return fileRef.getDownloadURL()
.then(url => {
fileUrls.push({ url: url, type: file.type }) // Adding urls to arr
})
})
Consider instead using async/await syntax to avoid all these callbacks and make the code easier to read.

Related

Fetch and Store files with IndexedDB

I need to download a list of files and store them locally with IndexedDB. I am using fetch to retrieve the files as follows:
cacheRecordings() {
var request = window.indexedDB.open("database", 2);
request.onsuccess = event => {
var database = event.target.result;
var transaction = database.transaction(["store"], 'readwrite'); //second step is opening the object store
this.objectStore = transaction.objectStore("store");
}
for (const url of this.urls) {
fetch(url)
.then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const index = this.objectStore.index('path');
index.openCursor().onsuccess = function(event) { <-- Error is thrown here
this.objectStore.add(url, path);
}.bind(this)
})
.catch((error) => {
console.log(error)
})
}
}
The above code results in the following two errors:
Failed to execute 'openCursor' on 'IDBIndex': The transaction is not active.
Failed to execute 'index' on 'IDBObjectStore': The transaction has finished
How do I store the fetched files using IndexedDB?
I found a relevant question - but it does NOT address my use case of fetched files.
TransactionInactiveError: Failed to execute 'add' on 'IDBObjectStore': The transaction is not active
My guess is that this happens because you are performing an async operation (the fetch) within a sync loop (the for)
To confirm this try storing a single file in the db without the loop. If that's successful, look into executing acync code within a loop

Is there a way to prevent getting photos randomly on Firebase Storage?

I have some folders on Firebase Storage. Every folder includes 1_org_zoom.jpg file. I can get those photos, but not in the right array order. I want to get those photos in an order like the photo below, but photos are shown randomly. I tried for loop and forEach, but didn't work. Is there any way to get these photos in an order?
useEffect(() => {
/* for(let i = 0; i<products.length; i++){
const imgRef = ref(storage, imgFolderRef[i]);
console.log(imgRef);
getDownloadURL(imgRef)
.then((url) => {
console.log(url);
})
}
*/
imgFolderRef.forEach((imgFolder) => {
console.log(imgFolder);
getDownloadURL(ref(storage, imgFolder))
.then((url) => {
setExArr((oldArr) => [...oldArr, url])
})
})
}, [products]);
Array name is imgFolderRef.
The whole point of asynchronous functions is not to block the execution of the script so that several operations can run concurrently.
In your case getDownloadURL and setExArr are asynchronous, as a consequence you have no guaranty concerning the ordering of the images. What you can do is to explicitly sort the array:
setExArr((oldArr) => [...oldArr, url].sort())

React Native API FETCH Different names for each objects

I am connecting a REST api from React Native app. I have Json response with filename objects with different names but all the objects have same variables: filename, message, and display.
Number of objects changes with each request to API (REST), the names of objects in response are different depending on requests. But the variables in each object are same as above.
The information I need from this response is only filename text, but it will be acceptable if I get list of objects so I can read through the messages from errors.
The image shows how my objects look like.
This is my fetch request :
const getGists = async () => {
await axios
.get(`https://api.github.com/gists/public?per_page=30`)
.then((r) => {
let n;
for (n = 0; n < 30; n++) {
console.log(r.data[n].files.filename);
// console.log("____________________");
// console.log(r.data[n].owner.avatar_url);
// console.log("____________________");
// console.log(JSON.stringify(r.data[n].files));
}
})
.catch((e) => {
console.log("ERROR", e);
});
};
how is possible to get every filename from these requests even if object name is not the same in each iteration . Thanks for help
Working with the result of the API calls and some higher-order functions, this will work fine:
const getGists = async () => {
await axios
.get(`https://api.github.com/gists/public?per_page=30`)
.then((response) => {
const myDesireResult = response.data.reduce((acc, item) => {
const files = Object.values(item.files);
if (files.length > 1) {
files.forEach((file) => acc.push(file.filename));
} else {
acc.push(files[0].filename);
}
return acc;
}, []);
console.log(myDesireResult);
})
.catch((e) => {
console.log("ERROR", e);
});
};
Explanation:
in the then block, can get the API call result with result.data
with reduce function, looping through the data will start.
since the object in the files has different names, we can get the files with Object.values() easily.
Some of the files contain several items and most of them have just one item. so with checking the length of the file we can do proper action. if the files have more than one element, with another simple lop, we can traverse this file array easily.
Check the working example on codesandbox

Trouble with "forEach" with Firebase Firestore .where() .get() Snapshots

I can only give an example of my code, since my real code is too big. I'm working with Firebase Cloud Functions and Firebase Firestore.
First I define a returnData dictionary (within my Firebase Cloud Function):
let returnData = {};
Then I get() "snapshots" of my db:
const db = admin.firestore();
const snapshotUsers = await db
.collection("users")
.where("followers", "array-contains", exampleId)
.get();
Then, I'm trying to populate the returnData dictionary:
snapshotUsers.forEach(async (docUsers) => {
await db
.collection("users")
.doc(docUsers.id)
.collection("profileProjects")
.listDocuments()
.then(async (documentRefsProjects) => {
if (!(Object.keys(documentRefsProjects).length === 0)) {
return await db.getAll(...documentRefsProjects);
}
})
.then(async (documentSnapshotsProjects) => {
if (!!documentSnapshotsProject) {
for (let documentSnapshotProject of documentSnapshotsProjects) {
if (documentSnapshotProject.exists) {
// ----------- Populate here -----------
returnData = {
...returnData,
[documentSnapshotProject.id]: {
followers: docUsers.data().followers,
}
};
// --------------------------------------
}
}
}
})
});
// Send response when forEach is done with returnData
res.status(201).send({ returnData: returnData });
Problem is: once it successfully populates returnData, it then throws that data away and
res.status(201).send({ returnData: returnData }); returns the initial empty dictionary
I understand that: "forEach() executes the callback function once for each array element; unlike map() or reduce() it always returns the value undefined and is not chainable", but I seems like I have no other option than to use forEach for firestore, so I'm stuck.
I tried using the map() function instead, but Firestore returns TypeError: snapshotUsers.map is not a function.
I even tried doing a manual for...in and for...of loop, but those also gave me errors when I would try to call id on docUsers:
for (let docUsers of snapshotUsers) {
await db
.collection("users")
.doc(snapshotUsers[docUsers].id) // <---- error'd here
I'm having a real tough time here since the only way I can test this is deploying the Firebase Cloud Function every time.
I wondering if anyone can help me out here. Any advice or solution helps.
I'm having a real tough time here since the only way I can test this is deploying the Firebase Cloud Function every time.
check it: https://firebase.google.com/docs/hosting/test-preview-deploy
I used to work with:
firebase serve --only functions
And it just work fine.
For your problem...
Your foreach is ending before your returnData is populated.
Foreach has one argument that show the current data index in your array.
So, in your success callback, you can check if it is the last array's data then just send your response.
Something like this:
let returnData;
xpto.forEach((data,index)=>{
db.then(()=>{
//populates your returnData
if(index === xpto.lenght-1) res.status(201).send({ returnData })
})
})
Edit with askers experience:
for some reason didn't work with the snapshots, I had to initialize an
index outside the forEach and increment it within the .then()
function.

Extracting multiple observables into an already mapped stream angular rxjs

Currently building a project in Firebase and Angular 4. I am pulling in an object of data which contains many keys of data, and a lot of different uid keys.
To keep the data somewhat manageable i don't want to save the object data inside some keys where it can be changed at the host.
In the instance i have the uid stored, i want to create a new key of the extracted observable. I can easily add it as an observable inside the mapped object but i want to actually extract the observable into the object during the stream. Here is what i currently have:
this.ss.data()
.map(res => {
res['test1'] = this.fb.serie(res['serieUid']).map(res => res)
return res
})
.subscribe(res => {
console.log(res)
})
Result -
serieUid:"-Kk1FeElvKMFcyrhQP4T"
state:"Tennessee"
test1:FirebaseObjectObservable
timezone:"US/Eastern"
But i would like to have the extracted observable inside 'test1', I have failed on this for a few hours on this and am confused. There is more than one instance of a uid, so this was have to happen multiple times. Then subscribe.
Follow up after answered below:
Heres what i ended up with in my final function
getRound(roundUid: string) {
/**
Retrieves a round based simply on the uid
supplied.
**/
return this.fb.round(roundUid)
.mergeMap(round => // Get Serie Data
this.fb.serie(round['serieUid']).map(serie => {round['serie'] = this.cs.cleanForFb(serie); return round})
)
.mergeMap(round => // Get Type Data
this.fb.type(round['typeUid']).map(type => {round['type'] = this.cs.cleanForFb(type); return round})
)
.mergeMap(round => // Get sx Coast Data
this.fb.coast(round['sxCoastUid']).map(sxCoast => {round['sxCoast'] = this.cs.cleanForFb(sxCoast); return round})
)
}
Try this. The mergeMap() operator subscribes to the internal Observable whose result is using map() turned into the original res updated with test1.
this.ss.data()
.mergeMap(res => this.fb.serie(res['serieUid'])
.map(innerRes => {
res['test1'] = innerRes;
return res;
})
)
.subscribe(res => {
console.log(res)
})

Categories

Resources