I have a model in Firebase, who is saved like this:
Im fetching the data in my componentDidMount like this:
fetchMatches = async () => {
const { firebaseApp, auth, pool } = this.props;
await firebaseApp.database()
.ref(`/pools/${pool.key}/users/${auth.uid}/`)
.once('value')
.then(snapshot =>{
this.setState({matches:snapshot.val()})
})
}
The problem is, that my state, becomes an object: Not an Array, not a list.
How can I read this data, in a proper way that I can filter it based on an atribute.
When I try to do
let matches = this.state.matches;
for (let index = 0; index < matches.length; index++) {
const element = matches[index];
console.log(element);
}
It not works.
When I try to use a this.state.matches.map() they say that is not a function. And it really isnt as on my debugger this.state.matches is an OBJECT.
What Im doing wrong here?
When you are fetching data with the once function it returns an object of that specific ref, in that case, it will return this ${auth.uid} data as an object.
So you might want to change your ref a little bit to: ref(/pools/${pool.key}/users/${auth.uid}/matches),
then to loop through the matches children, according to the firebase documentation https://firebase.google.com/docs/database/web/lists-of-data#listen_for_value_events
await firebaseApp.database()
.ref(`/pools/${pool.key}/users/${auth.uid}/matches`)
.once('value')
.then(snapshot =>{
snapshot.forEach(function(childSnapshot) {
var childKey = childSnapshot.key;
var childData = childSnapshot.val();
// ...
});
})
Related
I am trying to make barbershop web app where costumer can see list of free appointments and when they reserve free appointment I want to delete that field from firebase.
I have a collection which represents one barber.
This is how it looks in firebase.
As you see radno_vrijeme is object or map in firebase which contains 6 arrays, and in each array there is list of free working hours.
In my function I am able to do everthing except last line where I need to update firebase collection.
const finishReservation = async () => {
try {
const freeTimeRef = collection(db, `${barber}`);
const q = query(freeTimeRef);
const querySnap = await getDoc(q);
querySnap.forEach(async (doc) => {
const radnoVrijeme = doc.data().radno_vrijeme;
// Find the index of the hour you want to delete
const index = radnoVrijeme["Mon"].indexOf(hour);
// Remove the hour from the array
radnoVrijeme["Mon"].splice(index, 1);
// Update the document in the collection
console.log(radnoVrijeme);
const radnoVrijemeMap = new Map(Object.entries(radnoVrijeme));
await freeTimeRef.update({ radno_vrijeme: radnoVrijemeMap });
});
} catch (error) {
console.log(error);
}
};
I tried to pass it as JSON stringified object, but it didn't work. I always get this error :
"FirebaseError: Expected type 'ya', but it was: a custom Ia object"
When you are trying to fetch multiple documents using a collection reference or query, then you must use getDocs():
const finishReservation = async () => {
try {
const freeTimeRef = collection(db, `${barber}`);
const q = query(freeTimeRef);
const querySnap = await getDocs(q);
const updates = [];
querySnap.forEach((d) => {
const radnoVrijeme = d.data().radno_vrijeme;
const index = radnoVrijeme["Mon"].indexOf(hour);
radnoVrijeme["Mon"].splice(index, 1);
const radnoVrijemeMap = new Map(Object.entries(radnoVrijeme));
updates.push(updateDoc(d.ref, { radno_vrijeme: radnoVrijemeMap }))
});
await Promise.all(updates);
console.log("Documents updated")
} catch (error) {
console.log(error);
}
};
getDoc() is used to fetch a single document using a document reference.
Hi I have exported using data (hawkers collection) using getDocs() from Firebase.
After that I put each hawker data as an object in an array called allStall as shown in the screenshot of the console log below.
Question 1 - How do I access each individual object in my allStall array. I try to use .map() to access each of it, but i am getting nothing.
Do note that I already have data inside my allStall array, see screenshot above.
[Update] map doesn't work in code below because field is stallname not stallName. However, it needs to be async + await if using/call in/from other function.
Question 2 - Why is there [[Prototype]]: Array(0) in my allStall array
export /*Soln add async*/function getAllStall(){
var allStall = [];
try
{
/*Soln add await */getDocs(collection(db, "hawkers")).then((querySnapshot) =>
{
querySnapshot.forEach((doc) =>
{
var stall = doc.data();
var name = stall.stallname;
var category = stall.category;
var description = stall.description;
var stallData = {
stallName:name,
stallCategory:category,
stallDescription:description
};
allStall.push(stallData);
});});
console.log(allStall);
//Unable to access individual object in Array of objects
allStall.map(stall =>{console.log(stall.stallName);});}
catch (e) {console.error("Error get all document: ", e);}
return allStall;
}
In my main js file, i did the following:
useEffect(/*Soln add await*/() =>
{
getAllStall();
/*Soln:replace the statement above with the code below
const allStall = await getAllStall();
allStall.map((stall)=>console.log(stall.stallname));
*/
}
);
You are getting nothing because allStall is empty since you are not waiting for the promise to be fullfilled
try this
export const getAllStall = () => getDocs(collection(db, "hawkers"))
.then((querySnapshot) =>
querySnapshot.map((doc) =>
{
const {stallName, category, description} = doc.data();
return {
stallName:name,
stallCategory:category,
stallDescription:description
};
});
)
try to change use effect like this
useEffect(async () =>
{
const allStats = await getAllStall();
console.log(allStats)
allStats.forEach(console.log)
}
);
A very big thanks to R4ncid, you have been an inspiration!
And thank you all who commented below!
I managed to get it done with async and await. Latest update, I figure out what's wrong with my previous code too. I commented the solution in my question, which is adding the async to the function and await to getDocs.
Also map doesn't work in code above because field is stallname not stallName. However, it needs to be async + await if using in/calling from other function.
Helper function
export async function getAllStall(){
const querySnapshot = await getDocs(collection(db, "hawkers"));
var allStall = [];
querySnapshot.forEach(doc =>
{
var stall = doc.data();
var name = stall.stallname;
var category = stall.category;
var description = stall.description;
var stallData = {
stallName:name,
stallCategory:category,
stallDescription:description
};
allStall.push(stall);
}
);
return allStall;
}
Main JS file
useEffect(async () =>
{
const allStall = await getAllStall();
allStall.map((stall)=>console.log(stall.stallname));
}
);
Hurray
Is it possible to get an array of objects from Firestore. I tried something like below but I am getting undefined when I tried to log comments[0].comment
let comments = [{}]
try {
const ref = firebase
.firestore()
.collection('comments')
.where('ytid', '==', id)
const commentSnapshot = await ref.get()
let comments = commentSnapshot
console.log('comment snapshot')
console.log(comments[0].comment) //undefined
} catch (e) {
console.log(e)
}
I have figured it out. I did it like below and it works.
let comments = []
try {
const ref = firebase
.firestore()
.collection('comments')
.where('ytid', '==', id)
const commentSnapshot = await ref.get()
commentSnapshot.forEach((doc) => {
var obj = {}
obj['comment'] = doc.data().comment
obj['createdat'] = doc.data().createdat
obj['username'] = doc.data().username
obj['name'] = doc.data().name
obj['photourl'] = doc.data().photourl
comments.push(obj)
})
This returns a QuerySnapshot which contains the DocumentSnapshot of each document that has matched your query.
const commentsSnapshot = await firebase.firestore().collection('comments').where('ytid', '==', id).get()
The array of object is a field in your document. You cannot get a single field from a document. You need to fetch the document and then access that field hence you make that query above first.
Now commentsSnapshot.docs is an array of DocumentSnapshots. Now if you know there is only one matching document you can access it's data like this:
const firstCommentData = commentsSnapshot.docs[0].data()
//Access a specific field
const anyField = firstCommentData.anyField
In case your QuerySnapshot has multiple documents, you can loop thought the docs as it is an array.
//commentsSnapshot.forEach(...) works as well
commentsSnapshot.docs.forEach((doc) => {
console.log(doc.data())
})
I saw similar questions online but none of their solutions worked for me.
I am building an app in React Native which loads information from firebase and then displays it. I want to load objects from firebase, put them in an array and then set the state so the class would re-render and display it once it's loaded.
The information is being loaded fine, but I can't find a way to call setState after the array has loaded. I tried promises and tried using another function as a callback, but nothing had worked for me yet. It always executes setState before the array is loaded. I don't know if using setTimeout in some way would be a good solution though.
Here is the some of the code (I want to update the jArray in this.state and then re-render the page) :
constructor(props){
super(props);
this.state = {
jArray: []
}
}
componentDidMount(){
this.getJ();
}
async getJ(){
let jArray = [];
let ref = database.ref('users/' + fb.auth().currentUser.uid + '/usersJ');
let snapshot = await ref.once('value');
let itemProcessed = 0;
let hhh = await snapshot.forEach(ch => {
database.ref('J/' + ch.val()).once('value')
.then(function(snapshot1){
jArray.push(snapshot1);
itemProcessed++;
console.log(itemProcessed);
if(snapshot.numChildren()===jArray.length){
JadArray = jArray
}
})
});
}
Thanks (:
Maybe you can do something like this:
// don't forget to use an arrow function to bind `this` to the component
getJ = async () => {
try {
const ref = database.ref('users/' + fb.auth().currentUser.uid + '/usersJ');
const snapshot = await ref.once('value');
// it might be easier just to start by getting the data into an object you can use like this
const dataObj = snapshot.val();
// extract the keys
const childKeys = Object.keys(dataObj);
// use the keys to create a function that makes an array of all the promises we want
const createPromises = () =>
childKeys.map(childKey => database.ref('J/' + childKey).once('value'));
// await ALL the promises before moving on
const jArray = await Promise.all(createPromises());
// now you can set state
this.setState({ jArray });
// remember to catch any errors
} catch (err) {
console.warn(err);
// you might want to do something else to handle this error...
}
};
}
So in the end I found a solution to my problem, from: https://stackoverflow.com/a/47130806/3235603
My code looks like this:
async getJ(){
let jArray = [];
let ref = database.ref('users/' + fb.auth().currentUser.uid + '/usersJ');
let snapshot = await ref.once('value');
let itemProcessed = 0;
let that = this;
let hhh = await snapshot.forEach(ch => {
database.ref('J/' + ch.val()).once('value')
.then(function(snapshot1){
jArray.push(snapshot1);
itemProcessed++;
console.log(itemProcessed);
if(snapshot.numChildren()===jArray.length){
JadArray = jArray
that.setState({
jadArray : jArray,
dataLoaded : true
},() => console.log(that.state))
}
})
});
}
It's kinda a tricky one with 'this' and 'that', but it all works fine now.
I was learning react and doing some axios api call with an array. I did a code on gathering data through coinmarketcap api to learn.
So, my intention was to get the prices from the api with a hardcoded array of cryptocurrency ids and push them into an array of prices. But I ran into a problem with the prices array, as the prices were all jumbled up. I was supposed to get an array in this order
[bitcoinprice, ethereumprice, stellarprice, rippleprice]
but when I ran it in the browser, the prices came randomly and not in this order, sometimes I got my order, sometimes it didn't. I used a button which onClick called the getPrice method. Does anyone know what went wrong with my code? Thanks!
constructor(){
super();
this.state = {
cryptos:["bitcoin","ethereum","stellar","ripple"],
prices:[]
};
this.getPrice = this.getPrice.bind(this);
}
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
this.state.prices.push(data.price_usd);
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
}
}
If you want to receive the data in the order of the asynchronous calls you make, you can use Promise.all, that waits until all the promises of an array get executed and are resolved, returning the values in the order they were executed.
const cryptos = ['bitcoin', 'ethereum', 'stellar', 'ripple'];
const arr = [];
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
arr.push(axios.get(cryptoUrl));
}
Promise.all(arr).then((response) =>
response.map(res => console.log(res.data[0].name, res.data[0].price_usd))
).catch((err) => console.log(err));
You could use a closure in the for loop to capture the value of i and use it as the index once the data is returned rather than using push:
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++) {
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
(function (x) {
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
var newPrices = this.state.prices;
newPrices[x] = data.price_usd;
this.setState({prices: newPrices});
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
})(i);
}
}