Painful querying API with firebase? - javascript

I'm starting to learn firebase with firestore.
I have spent more hours than I would've like understanding the reference type and trying to get it to work with a simple query that references a portfolio's category.
This is the code:
try {
const portfolioSnap = await db.collection("portfolio").get();
let portfolioDoc = portfolioSnap.docs;
let categoriesRef = [];
portfolioDoc.forEach(p => {
categoriesRef.push(p.data().category.get());
});
let categories = await Promise.all(categoriesRef);
let portfolio = [];
portfolioDoc.map((p, i) => {
let portfolioObject = {
...p.data(),
category: categories[i].data().name
};
portfolio.push(portfolioObject);
});
return portfolio;
} catch (error) {
console.warn("ERROR: ", error);
}
I'm not sure if this makes sense.
I'm trying to get the category for each portfolio document but I feel this is over-engineered or I'm totally doing it the wrong way.
And this is not counting if I have references for images or files which I feel would make things... well, not pretty.

Nothing strange here. This is the way that nosql databases work (since there is no join operation, nor is there any explicit relationships between documents other than what you define).

Related

Is it possible to loop through an API, print separate results, and then combine them into a single variable?

I’m trying to read the sentiment of multiple Reddit posts. I’ve got the idea to work using 6 API calls but I think we can refactor it to 2 calls.
The wall I’m hitting - is it possible to loop through multiple APIs (one loop for each Reddit post we’re scrapping), print the results, and then add them into a single variable?
The last part is where I’m stuck. After looping through the API, I get separate outputs for each loop and I don’t know how to add them into a single variable…
Here’s a simple version of what the code looks like:
import React, { useState, useEffect } from 'react';
function App() {
const [testRedditComments, setTestRedditComments] = useState([]);
const URLs = [
'https://www.reddit.com/r/SEO/comments/tepprk/is_ahrefs_worth_it/',
'https://www.reddit.com/r/juststart/comments/jvs0d1/is_ahrefs_worth_it_with_these_features/',
];
useEffect(() => {
URLs.forEach((URL) => {
fetch(URL + '.json').then((res) => {
res.json().then((data) => {
if (data != null) setTestRedditComments(data[1].data.children);
});
});
});
}, []);
//This below finds the reddit comments and puts them into an array
const testCommentsArr = testRedditComments.map(
(comments) => comments.data.body
);
//This below takes the reddit comments and terns them into a string.
const testCommentsArrToString = testCommentsArr.join(' ');
console.log(testCommentsArrToString);
I've tried multiple approaches to adding the strings together but I've sunk a bunch of time. Anyone know how this works. Or is there a simpler way to accomplish this?
Thanks for your time and if you need any clarification let me know.
-Josh
const URLs = [
"https://www.reddit.com/r/SEO/comments/tepprk/is_ahrefs_worth_it/",
"https://www.reddit.com/r/juststart/comments/jvs0d1/is_ahrefs_worth_it_with_these_features/",
];
Promise.all(
URLs.map(async (url) => {
const resp = await fetch(url + ".json");
return resp.json();
})
).then((res) => console.log(res));
I have used Promise.all and got the response and attached a react sandbox with the same.
Based on your requirements, you can use state value or you can prepare your api response before setting it to state.

Batch write with Firebase Cloud Functions

I'm using Firebase as backend to my iOS app and can't figure out how to construct a batch write through their Cloud Functions.
I have two collections in my Firestore, drinks and customers. Each new drink and each new customer is assigned a userId property that corresponds to the uid of the currently logged in user. This userId is used with a query to the Firestore to fetch only the drinks and customers connected to the logged in user, like so: Firestore.firestore().collection("customers").whereField("userId", isEqualTo: Auth.auth().currentUser.uid)
Users are able to log in anonymously and also subscribe while anonymous. The problem is if they log out there's no way to log back in to the same anonymous uid. The uid is also stored as an appUserID with the RevenueCat SDK so I can still access it, but since I can't log the user back in to their anonymous account using the uid the only way to help a user access their data in case of a restoring of purchases is to update the userId field of their data from the old uid to the new uid. This is where the need for a batch write comes in.
I'm relatively new to programming in general but I'm super fresh when it comes to Cloud Functions, JavaScript and Node.js. I dove around the web though and thought I found a solution where I make a callable Cloud Function and send both old and new userID with the data object, query the collections for documents with the old userID and update their userId fields to the new. Unfortunately it's not working and I can't figure out why.
Here's what my code looks like:
// Cloud Function
exports.transferData = functions.https.onCall((data, context) => {
const firestore = admin.firestore();
const customerQuery = firestore.collection('customers').where('userId', '==', `${data.oldUser}`);
const drinkQuery = firestore.collection('drinks').where('userId', '==', `${data.oldUser}`);
const customerSnapshot = customerQuery.get();
const drinkSnapshot = drinkQuery.get();
const batch = firestore.batch();
for (const documentSnapshot of customerSnapshot.docs) {
batch.update(documentSnapshot.ref, { 'userId': `${data.newUser}` });
};
for (const documentSnapshot of drinkSnapshot.docs) {
batch.update(documentSnapshot.ref, { 'userId': `${data.newUser}` });
};
return batch.commit();
});
// Call from app
func transferData(from oldUser: String, to newUser: String) {
let functions = Functions.functions()
functions.httpsCallable("transferData").call(["oldUser": oldUser, "newUser": newUser]) { _, error in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
print(code)
print(message)
print(details)
}
}
}
}
This is the error message from the Cloud Functions log:
Unhandled error TypeError: customerSnapshot.docs is not iterable
at /workspace/index.js:22:51
at fixedLen (/workspace/node_modules/firebase-functions/lib/providers/https.js:66:41)
at /workspace/node_modules/firebase-functions/lib/common/providers/https.js:385:32
at processTicksAndRejections (internal/process/task_queues.js:95:5)
From what I understand customerSnapshot is something called a Promise which I'm guessing is why I can't iterate over it. By now I'm in way too deep for my sparse knowledge and don't know how to handle these Promises returned by the queries.
I guess I could just force users to create a login before they subscribe but that feels like a cowards way out now that I've come this far. I'd rather have both options available and make a decision instead of going down a forced path. Plus, I'll learn some more JavaScript if I figure this out!
Any and all help is greatly appreciated!
EDIT:
Solution:
// Cloud Function
exports.transferData = functions.https.onCall(async(data, context) => {
const firestore = admin.firestore();
const customerQuery = firestore.collection('customers').where('userId', '==', `${data.oldUser}`);
const drinkQuery = firestore.collection('drinks').where('userId', '==', `${data.oldUser}`);
const customerSnapshot = await customerQuery.get();
const drinkSnapshot = await drinkQuery.get();
const batch = firestore.batch();
for (const documentSnapshot of customerSnapshot.docs.concat(drinkSnapshot.docs)) {
batch.update(documentSnapshot.ref, { 'userId': `${data.newUser}` });
};
return batch.commit();
});
As you already guessed, the call customerQuery.get() returns a promise.
In order to understand what you need, you should first get familiar with the concept of promises here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
For your use case, you will probably end up with either using the then callback:
customerQuery.get().then((result) => {
// now you can access the result
}
or by making the method call synchronous, by using the await statement:
const result = await customerQuery.get()
// now you can access the result

Concept involved in using nested Knex

In my programming in Node.JS I made some code that worked, but I want to understand the concept of how it works.
In the code in question I use knex to retrieve information from a MySQL database. I import the knex module for that:
const config = {...}
const knex = require('knex')(config)
So far nothing new, only this time I needed to do a nested query and it was the first time. In this case, I consulted the sales data and then the sale items. I did it as follows:
const getSales = async () => {
await knex("tbl_vendas")
.select(knex.raw("tbl_vendas.id_vendas, tbl_vendedores.nome AS vendedor, " +
"tbl_clientes.nome_razaosocial AS cliente, tbl_vendas.data, tbl_vendas.hora, " +
"tbl_vendas.cupom, tbl_vendas.total"))
.leftOuterJoin("tbl_vendedores", "tbl_vendedores.id_vendedores", "tbl_vendas.id_vendedores")
.leftOuterJoin("tbl_clientes", "tbl_clientes.id_clientes", "tbl_vendas.id_clientes")
.then(sales => {
const rows = sales.map(sale => {
return knex("tbl_vendas_itens")
.select("tbl_vendas_itens.id_vendas_itens AS id_item", "tbl_produtos.descricao",
"tbl_vendas_itens.qtde", "tbl_vendas_itens.vl_unitario", "tbl_vendas_itens.desconto",
"tbl_vendas_itens.vl_total")
.leftOuterJoin("tbl_produtos", "tbl_vendas_itens.id_produtos", "tbl_produtos.id_produtos")
.where("tbl_vendas_itens.id_vendas", "=", sale.id_vendas)
.then(sales_items => {
const newRow = { ...sale, itens: [...sales_items] }
return newRow
})
})
return Promise.all(rows)
})
.then(console.log);
}
Writing this code was pretty intuitive and it worked, but then I was amazed that I used the knex constant twice, one inside the other, and it didn't hurt.
I ran a console.log(typeof(knex)) to find out what it was and it returned that it is a function.
Could someone explain the theory behind the use of knex inside the other and help me understand why it is okay to do this?

How do I create an array from Promise results?

I'm using React to build a web app. At one point I have a list of ids, and I want to use those to retrieve a list of items from a database, get a list of metrics from each one, and then push those metrics to an array. My code so far is:
useEffect(() => {
const newMetrics = [];
currentItems.forEach((item) => {
const url = `items/listmetrics/${item.id}`;
Client.getData(url).then(async (metrics) => {
let promises = metrics.map((metricId: string) => {
// Get metric info
const urlMetric = `metrics/${metricId}`;
return Client.getData(urlMetric);
});
await Promise.all(promises).then((metrics: Array<any>) => {
metrics.forEach((metric: MetricModel) => {
const metricItem = {
id: metric.id,
metricName: metric.name
};
newMetrics.push(metricItem);
}
});
});
});
setMetrics(newMetrics);
});
}, [currentItems]);
where "metrics" is a state variable, set by setMetrics.
This appears to work ok, but when I try to access the resulting metrics array, it seems to be in the wrong format. If I try to read the value of metrics[0], it says it's undefined (although I know there are several items in metrics). Looking at it in the console, metrics looks like this:
However, normally the console shows arrays like this (this is a different variable, I'm just showing how it's listed with (2) [{...},{...}], whereas the one I've created shows as []):
I'm not confident with using Promise.all, so I suspect that that's where I've gone wrong, but I don't know how to fix it.

Reading from Firebase

I am very new to Firebase and JS itself as well, and it is not the easiest thing to understand.
So, I have a Firebase real time databse set up with dummy "users" profiles, that each contain different properties.
i am trying to call user's properties.
I have managed to achieved it with the following approach:
let installDateRef = database.ref(`users/${userAccount}/installationDate`);
let lastUsedDateRef = database.ref(`users/${userAccount}/lastUsed`);
installDateRef.on('value', function(snapshot) {
installationDate.innerText = (snapshot.val());
}, function (error) {
installationDate.innerText = `Error: ${error.code}`
});
lastUsedDateRef.on('value', function(snapshot) {
lastUsedDate.innerText = (snapshot.val());
}, function (error) {
lastUsedDate.innerText = `Error: ${error.code}`
});
But in my application I want to display all of the user information, so copying this ".on" function seems like overkill.
Someone said that there is cleaner approach like this below
const firebasePromises = [
database.ref(`users/${userAccount}/installationDate`),
database.ref(`users/${userAccount}/lastUsed`)
];
Promise.all(firebasePromises).then(([_installationDate, _lastUsed]) => {
installationDate.innerText = (_installationDate.val());
lastUsedDate.innerText = (_lastUsed.val());
})
But I honest to God cannot make it work. It either returns undefined or returns array of my promises const.
.val() does not work at all even, saying that this is not a function.
Any help would on the best and working practice would be soooo much appreciated.
Thank you
#Doug Stevenson thank you! This is exactly what was needed:
const firebasePromises = [
database.ref(`users/${userAccount}/installationDate`).once('value'),
database.ref(`users/${userAccount}/lastUsed`).once('value'),
];
Promise.all(firebasePromises).then(snap => {
installationDateHTML.innerText = snap[0].val();
lastUsedDateHTML.innerText = snap[1].val();
})

Categories