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.
Related
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
What's up friends,
I have a problem. I have DOM elements that show data from the variable (useState) that I am saving with some "accounts".
I have a function that updates the data of each element by calling the api, and I have a function that refreshes all the elements one by one, the problem is that when using the function that refreshes all the elements, the changes are not reflected correctly in the dom but when using the function individually (I have a button for that) I can see the changes correctly reflected.
I do not get the error, if you can help me I would be very grateful.
Refresh Function
let [accounts, setAccounts] = useState
const refreshAccount = async (id, name, token) => {
await setIsBusy(true)
await updateWorkingStatus(id, true, name, token)
let work = await axios.get(
'http://192.168.0.101:3000/account/refresh/' + id,
)
await updateWorkingStatus(id, false, name, token)
await setIsBusy(false)
// console.log(work.data.result)
let accs = [...accounts]
let index = accs.findIndex((acc) => acc._id === id)
accs[index] = work.data.result
accs[index]._id = id
// console.log(accs[index])
setTimeout(() => {
setAccounts(accs)
}, 0)
return 'OK'
}
Refresh Selected Item (I already have a function that selects each item and works nice)
const refreshSelected = async () => {
let finalAccounts = await accounts.filter((acc) => acc.selected)
for (let i = 0; i < finalAccounts.length; i++) {
await refreshAccount(
finalAccounts[i]._id,
finalAccounts[i].name,
finalAccounts[i].token,
)
}
}
I know the problem is updating the status, but I can't solve it. Implement a setTimeout and the previous element to the last is updated or the last of the cycle, the previous ones are left with the outdated data (first state).
I think problem is when using FOR loop on React, but dont know how to use it correctly
The problem is probably that the setAccounts function is asynchronous, but the execution doesn't wait for it to complete. Because setAccounts is asynchronous, subsequent calls in the same update cycle will overwrite previous updates, and the previous changes will be lost, causing only the last update to take effect.
You could store the changes to an array and update it after all the API requests have finished.
let [accounts, setAccounts] = useState
const refreshAccount = async (id, name, token) => {
await setIsBusy(true)
await updateWorkingStatus(id, true, name, token)
let work = await axios.get(
'http://192.168.0.101:3000/account/refresh/' + id,
)
await updateWorkingStatus(id, false, name, token)
await setIsBusy(false)
// console.log(work.data.result)
let accs = [...accounts]
let index = accs.findIndex((acc) => acc._id === id)
accs[index] = work.data.result
accs[index]._id = id
return accs
}
const refreshSelected = async () => {
let finalAccounts = await accounts.filter((acc) => acc.selected)
let newAccounts = []
for (let i = 0; i < finalAccounts.length; i++) {
const accs = await refreshAccount(
finalAccounts[i]._id,
finalAccounts[i].name,
finalAccounts[i].token,
)
newAccounts = newAccounts.concat(accs)
}
setAccounts(newAccounts)
}
I have the following code:
const readDataFromSql = () => {
// going to have to iterate through all known activities + load them here
let sql = "[...]"
return new Promise((resolve, reject) => {
executeSqlQuery(sql).then((dict) => {
let loadedData = [];
for (let key in dict) {
let newItemVal = new ItemVal("reading hw", 7121, progress.DONE);
loadedData.push(newItemVal);
}
resolve(loadedData);
});
});
}
ItemVal implementation:
class ItemVal {
constructor(name, time, type) {
this.name = name
this.time = time
this.type = type
}
}
Let's assume that newItemVal = "reading hwj", 5081, progress.PAUSED when readDataFromSql() first runs.
readDataFromSql() is then again called after some state changes -- where it repulls some information from a database and generates new values. What is perplexing, however, is that when it is called the second time, newItemVal still retains its old properties (attaching screenshot below).
Am I misusing the new keyword?
From what I can see in your example code, you are not mutating existing properties but creating a new object with the ItemVal constructor function and adding them to an array, that you then return as a resolved promise. Are you sure the examples you give a correct representation of what you are actually doing
Given that, I'm not sure what could be causing the issue you are having, but I would at least recommend a different structure for your code, using a simpler function for the itemVal.
Perhaps with this setup, you might get an error returned that might help you debug your issue.
const itemVal = (name, time, type) => ({ name, time, type })
const readDataFromSql = async () => {
try {
const sql = "[...]"
const dict = await executeSqlQuery(sql)
const loadedData = dict.map((key) =>
ItemVal("reading hw", 7121, progress.DONE)
)
return loadedData
} catch (error) {
return error
}
};
If the issue is not in the function, then I would assume that the way you handle the data, returned from the readDataFromSql function, is where the issue lies. You need to then share more details about your implementation.
const readDataFromSql = async () => {
let sql = "[...]"
------> await executeSqlQuery(sql).then((dict) => {
Use the await keyword instead of creating a new promise.
I did some modification and found that below code is working correctly, and updating the new values on each call.
const readDataFromSql = () => {
return new Promise((resolve, reject) => {
let loadedData = [];
let randomVal = Math.random();
let newItemVal = new ItemVal(randomVal*10, randomVal*100, randomVal*1000);
loadedData.push(newItemVal);
resolve(loadedData);
});
}
Could you recheck if you are using below line in the code, as it will instantiate object with same properties again and again.
let newItemVal = new ItemVal("reading hw", 7121, progress.DONE);
You can modify your code as below to simplify the problem.
const readDataFromSql = async () => {
// going to have to iterate through all known activities + load them here
let sql = "[...]" // define sql properly
let result = await executeSqlQuery(sql);
let loadedData = [];
for (let row in result) {
let newItemVal = new ItemVal(row.name, row.time, row.type);
loadedData.push(newItemVal);
}
return loadedData;
}
class ItemVal {
constructor(name, time, type) {
this.name = name
this.time = time
this.type = type
}
}
What you are talking about is an issue related to Object mutation in Redux, however, you didn't add any redux code. Anyway, you might be making some mistake while recreating(not mutating) the array.
General solution is the use spread operator as:
loadedData = [ ...loadedData.slice(0) , ...newloadedData]
In Dropdown.js line 188 instead of console.log-ing your variable write debugger;
This will function as a breakpoint. It will halt your code and you can inspect the value by hovering your mouse over the code BEFORE the newItemVal is changed again.
I can see in your screenshot that the newItemVal is modified again after you log it.
So I'm trying to connect to external server called Pexels to get some photos. I'm doing that from node.js but it is just a javascript issue. Pexels unfortunately lets user to download object with only 40 pictures per page.
https://api.pexels.com/v1/curated?per_page=40&page=1 // 40 is maximum
But actually I need more then that. I'd like to get 160 results, ie. to combine all first four pages. In order to do that I tried looping the request:
let pexelsData = [];
for(let i = 1; i < 5; i++) {
const randomPage = getRandomFromRange(1, 100); //pages should be randomized
const moreData = await axios.get(`https://api.pexels.com/v1/curated?per_page=40&page=${randomPage}`,
createHeaders('bearer ', keys.pexelsKey));
pexelsData = [ ...moreData.data.photos, ...pexelsData ];
}
Now I can use pexelsData but it work very unstable, sometimes it is able to get all combined data, sometimes it crashes. Is there a correct and stable way of looping requests?
You work with 3rd party API, which has rate limits. So you should add rate limits to your code. The simplest solution for you is using p-limit or similar approach form promise-fun
It will looks like that:
const pLimit = require('p-limit');
const limit = pLimit(1);
const input = [
limit(() => fetchSomething('foo')),
limit(() => fetchSomething('bar')),
limit(() => doSomething())
];
(async () => {
// Only one promise is run at once
const result = await Promise.all(input);
console.log(result);
})();
you can break it into functions like..
let images=[];
const getResponse = async i=> {
if(i<5)
return await axios.get(`https://api.pexels.com/v1/curated?per_page=40&page=${i}`)
}
const getImage = (i)=>{
if(i<5){
try {
const request = getResponse(i);
images = [...images,...request];
// here you will get all the images in an array
console.log(images)
getImage(++i)
} catch (error) {
console.log("catch error",error)
// getImage(i)
}
}
}
getImage(0); //call initail
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);
}
}