Read Data from Firebase and save it into an Array - React - reactjs

i just can't figure out why i can't save my loaded data into an array.
i`m trying to push the data to the array once the data is fully loaded (Within then())
Any idea why it's not working?
Many thanks :)
useEffect(() => {
fetchData = async () => {
let tempArray = [];
await firebase.firestore().collection('users').get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
firebase.firestore().collection('users').doc(doc.id).collection('posts').get().then((snapShot) => {
snapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
})
})
console.log(tempArray) // Output: Array []
}
fetchData();
}, [])

.forEach IS NOT ASYNCHRONOUS - it WILL NOT wait for your inner-loop .get()s. You need to do something like:
await firebase.firestore().collection('users').get().then((querySnapshot) => {
Promise.all(querySnapshot.map((doc) => {
firebase.firestore().collection('users').doc(doc.id).collection('posts').get().then((snapShot) => {
snapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
})
)
})
In addition - this seems pretty dang inefficient - since you're fetching ALL your users, and ALL their posts, you could just use a collectionGroup is a SINGLE round-trip, then sort by .parent if you need sorting (you don't show any such need in the example presented)
await firebase.firestore()..collectionGroup('posts').get().then((querySnapShot) => {
querySnapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})
})
Finally, you're mixing async/await with .then() syntax, which is generally not recommended:
// .get() is asynchronous
const querySnapShot = await firebase.firestore()..collectionGroup('posts').get();
// push() is synchronous, so need for await
querySnapShot.forEach((newDoc) => {
tempArray.push({
id: doc.id,
data: newDoc.data()
})
})

Related

Firebase promise - unable to set state, state array is empty

I have a promise to get a firebase collection and then return respective data (all according firebase documentation).
The data is returning fine, the only problem is that it seems that I am unable to set the collection data to a state array. When console.log(chats)this returns an empty array.
The data is returning fine as adding a console.log(doc.data()) in the first then logs the data ok but then it is empty.
What am I doing wrong in the promise?
const HomeScreen: React.FC<Props> = ({ navigation}) => {
const [chats, setChats] = useState<{[key: string]: any}>([]);
useEffect(() => {
const chatsArray = [{}];
db.collection("chats")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
chatsArray.push({
id: doc.id,
data:doc.data(),
})
});
})
.then(() => {
setChats(chatsArray)
console.log(chats);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
}, []);
return (
<SafeAreaView>
<ScrollView style={styles.container}>
{chats go here}
</ScrollView>
</SafeAreaView>
)
}
export default HomeScreen;
The console.log(chats); doesn't work, since updating the state is a asynchronous operation that won't have completed by the time you log the value. Try console.log(chatsArray); to see the actual value.
In addition, consider passing the array as the result along the promise chain instead of using a variable in the context:
db.collection("chats")
.get()
.then((querySnapshot) => {
return querySnapshot.docs.map((doc) => {
return {
id: doc.id,
data:doc.data(),
}
});
})
.then((chatsArray) => {
setChats(chatsArray)
console.log(chatsArray);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
Or simpler and with the same result:
db.collection("chats")
.get()
.then((querySnapshot) => {
const chatsArray = querySnapshot.docs.map((doc) => {
chatsArray.push({
id: doc.id,
data:doc.data(),
})
});
setChats(chatsArray)
console.log(chatsArray);
})
.catch((error) => {
console.log("Error getting documents: ", error);
});

Firebase: Problem getting data from nested onSnapshot

I have the following useEffect set up to fetch some data from firebase to populate a flatlist
useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
querySnapshot.forEach((doc) => {
const { sensorReadingsId } = doc.data();
// This works, but doesn't load the additional data I need.
// list.push({ ...{ id: doc.id }, ...doc.data() });
// This doesn't seem to load anything.
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
list.push({ ...{ id: doc.id }, ...doc.data(), ...documentSnapshot.data() });
});
});
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
});
}, [currentAccountId, currentShopId]);
Here's the problem: my flatlist doesn't load anything. When I initially load the screen, that console.log outputs an empty array. If I force it to re-render by saving the screen file, the array is correctly populated.
As noted in that comment, if I just use list.push({ ...{ id: doc.id }, ...doc.data() }); instead of db.collection('sensors')... it loads the sensors fine, but without the additional data that I need.
How do I resolve this? Thanks in advance!
Solution: As #Rajitha Udayanga has pointed out, checking the length of the first querysnapshot against the list array is a great way to check whether or not all the data has been loaded. Here's the working code (note the conditional that setSensorsData has been moved into):
useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
querySnapshot.forEach((doc) => {
const { sensorReadingsId } = doc.data();
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
list.push({ ...{ id: doc.id }, ...doc.data(), ...documentSnapshot.data() });
// Fix is here:
if (querySnapshot.docs.length === list.length) {
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
}
});
});
});
}, [currentAccountId, currentShopId]);
Disclaimer: I have zero experience with React.
In Angular I deal with such issues using RxJS's combineLatest.
However, you can try this one:
useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
const useValues = () => {
if (list.some(value => value === undefined)) {
return; // not everything has loaded yet - abort
}
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
}
querySnapshot.forEach((doc, index) => {
list.push(undefined);
const { sensorReadingsId } = doc.data();
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
list[index] = ({ id: doc.id, ...doc.data(), ...documentSnapshot.data() });
useValues();
});
});
});
}, [currentAccountId, currentShopId]);
And don't forget to detach the listeners when you don't need them anymore.
useEffect(() => {
return db
.collection('accounts')
.doc(currentAccountId)
.collection('shops')
.doc(currentShopId)
.collection('sensors')
.onSnapshot((querySnapshot) => {
const list = [];
querySnapshot.forEach((doc) => {
const { sensorReadingsId } = doc.data();
db.collection('sensors')
.doc(sensorReadingsId.toString())
.onSnapshot((documentSnapshot) => {
const newData = {
...{ id: doc.id },
...doc.data(),
...documentSnapshot.data()
};
const itemIndex = list.findIndex(item => item.id ===
newData.id);
if (itemIndex !== -1) {
list[itemIndex] = newData;
} else {
list.push(newData);
}
// do it like thi
if (querySnapshot.docs.length === list.length) {
setSensorsData(list);
console.log(sensorsData);
setLoading(false);
}
});
});
});
}, [currentAccountId, currentShopId]);

How to post constant and dynamic data in axios?

const insertProductInfoToDatabase = () => {
products.map((product, index) => {
axios
.post(
ProductPostAPI,
{
orderNo: orderId,
customerId: "CD1",
itemId: product.prd_ID,
itemQnty: product.length,
itemRate: product.prd_Rate,
itemPrice: product.prd_Rate,
},
config
)
.then((response) => {
return response;
})
.catch((err) => {
console.log(err);
});
});
};
I have two constant data and four dynamic data and I want to post all these six data into my database through Axios. I'm a little bit confused about how to send data?

Redux Action return undefined

So, I’m building an Expense App with React Native and Redux and I have this two actions:
export const getExpenses = () => async (dispatch) => {
await db.onSnapshot((querySnapshot) => {
const data = [];
querySnapshot.forEach((doc) => {
const { description, name, value, date, type } = doc.data();
data.push({
key: doc.id,
doc, // DocumentSnapshot
description,
date,
name,
value,
type,
});
});
dispatch({
type: TYPES.GET_EXPENSES,
payload: data,
});
dispatch({
type: TYPES.SET_LOADING_EXPENSES,
payload: false,
});
console.log('getExpenses', data);
});
};
export const filterMonthInfo = () => async (dispatch, getState) => {
const info = getState().monthExpenses.data; // data returned by getExpenses()
const currentMonth = getState().dateReducer.currentDate;
const filteredInfo = info
.filter(
(data) => moment(moment(data.date).format('DD/MM/YYYY')).format('MMMM YYYY') === currentMonth,
)
.sort((a, b) => new Date(b.date) - new Date(a.date));
dispatch({
type: TYPES.GET_FILTERED_EXPENSES,
payload: filteredInfo,
});
console.log('filtermonth', filteredInfo);
};
In the Screen where I want to use the data returned by filterMonthInfo i have the following useEffect:
useEffect(() => {
getExpenses();
filterMonthInfo();
getCurrentDate();
}, []);
But since getExpenses is an async function, filterMonthInfo will run first and is going to return undefined because this last function is filtered based on data returned by getExpenses.
What is the best approach so I can make getExpenses run first and then filterMonthInfo?
Thank you
If you want to run a code after an async call is finished, you have to wait for it using Promise. write the code as
useEffect(() => {
getExpenses()
.then(()=>{
filterMonthInfo();
getCurrentDate();
}
);
}, []);
or use async await as it makes syntax more clear

Store axios (JSON) response in array

I have an array which is being used by another file to map out the contents. I originally hard coded values into this array, but now I'd like to integrate an axios get call to retrieve data, and then store the info from the response into the array. I can successfully get the JSON response with the correct data, but I am stuck on getting the JSON response data into the array. Any help would be greatly appreciated
let theArray = [
{
id: '',
name: '',
},]
useEffect(() => {
axios
.get(`/api/data`)
.then(res => {
//? need to store res.data.id to theArray.id, and res.data.name to theArray.name
})
}, [])
You can simply push the response to the array, but you'd have to begin with an empty array, otherwise, the first element you have hardcoded will not have any data.
let theArray = []
useEffect(() => {
axios
.get(`/api/data`)
.then(res => {
const newItem = {
id: res.data.id,
name: res.data.name,
};
theArray.push(newItem);
})
}, [])
Here another solution, I think #Sylens solution is a good one, this is just a matter of structuring your code as you want
let theArray = []
useEffect(() => {
axios
.get(`/api/data`)
.then(res => {
// object destructuring
const { id, name } = res.data;
theArray.push({ id, name })
})
}, [])
If your data is in json format you should wait for the info to get parsed, something like this:
let theArray = [
{
id: '',
name: '',
},
];
useEffect(() => {
axios
.get(`/api/data`)
.then(res => res.json())
.then((result) => {
theArray[0].id = result.id;
theArray[0].name = result.name;
})
}, []);
Edit: if you want to add the new data just push it to the array
theArray.push({
id: result.id,
name: result.name,
})
If it's only modifying first element:
let theArray = [{ id: '', name: '',},]
useEffect(() => {
axios
.get(`/api/data`)
.then(res => {
theArray[0].id = res.data.id
theArray[0].name = res.data.name
})
}, [])

Resources