correct output of async await promise? - javascript

I'm using the firstore, and I get information from the first.
Here, I want to retrieve data from the firstore and print it out, but there is a problem with asynchronous mode.
The output is output after undefined.
I want to know how to print it correctly.
When data is received, the function receives data, and the method of receiving data after searching the firestore.
in react component
index = (date) =>{
let date_total = UserAction.getIndex(date)
return date_total
}
render(){
const {user_list} = this.props;
console.log(this.state)
const date = "2020-02-24"
console.log("data",date)
let index = this.index(date) < this
console.log("index", index)
return(...)
and useraction function
export function getIndex(data){
let time_now = moment(data).utc().format()
const user_history_start = moment(time_now).startOf("d").utc().format();
const user_history_end = moment(time_now).endOf("d").utc().format();
let db = loadFB().firestore();
let query = db.collection('users').where('create_date','>=',user_history_start).where('create_date','<=',user_history_end);
let number ;
return query.get().then( docs=>{
number = docs.size
return number
})
}
i want output
data 2020-02-24
index 11 < (firestore given data)
but output
data 2020-02-24
index promise{<pending>} < (firestore given data)
Give me a good solution.

I guess you can't print a promised value in render, set the state and print it instead?
constructor() {
super();
this.state = {
index: null
}
}
getIndex = (date) => {
// --------- update ----------
UserAction.getIndex(date).then(date_total => this.setState({ index: date_total }))
}
componentDidMount() {
const date = "2020-02-24"
// --------- update ----------
this.getIndex(date)
}
render() {
console.log("index", this.state.index)
// will first print null,
// then print the index when the promise is done (re-rendered due to state change)
}
You may want to read this as a reference.

Related

setState after loading an array from firebase

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.

Do not understand Uncaught Typerror Cannot Read Property of 0 Undefinded?

Creating my first ReactJS Website and using Node in the back-end, currently in the code that follows I fetch data that I then print on the page. I manage to print the names of the people in a project, their picture and their email from the server BUT the description of the project i get the error :
TypeError: Cannot read property '0' of undefined
Which I do not understand.
Here is the code :
class ProjectPage extends Component {
constructor(props) {
super(props);
this.state = {
user: [],
description: [],
mail: [],
name: [],
};
this.getNames = this.getNames.bind(this);
this.getPictures = this.getPictures.bind(this);
this.getMails = this.getMails.bind(this);
this.getDetails = this.getDetails.bind(this);
}
I create the class and all the elements that are required
componentDidMount() {
console.log("BEGIN componentDidMount");
this.fetchDetails();
this.fetchNames();
this.fetchMails();
console.log("END componentDidMount");
}
Call all the function in my componentDidMount()
fetchDetails() {
console.log("BEGIN fetchDetails()");
let url = 'http://192.168.1.33:8080/getprojectdetails/Aprite';
console.log("url details = " + url);
fetch(url)
.then(results => {
var json = results.json();
return json;
})
.then(data => {
this.setState({ description: data });
})
console.log("END fetchData()");
}
Here is the fetch of the project description
getDetails = () => {
let lines = [];
let nbr = this.state.description.length;
console.log("nbr = " + nbr);
if (nbr){
console.log("lines = " + this.state.description[0].P_Description);
for (let i = 0; i < nbr; i++)
lines.push(<div key={this.state.descripton[i].P_Description}></div>);
}
return (lines);
}
And the function to print the data in the Render() function
But when i try to print this data, the value of nbr passes from 0 to 1 then to 0 again... in the console log I can see the description but it doesn't appear on the website and I don't get it.
Please help me ?
There is a typo in the inner loop inside the getDetails function
You should write this.state.description not this.state.descripton
Hope this solves your problem :)
So with the React render lifecycle system, the componentDidMount will actually happen after the first render. During that first render, you're trying to access the first element of an empty array, which is the error you are seeing.
In order to solve this problem, in your render method, you should have a fallback something to render while we wait for the fetchDetails to return a value from the server. If you want it to render nothing, you can just return null.
ie.
const { description = [] } = this.state;
if (description.length === 0) {
return null;
}
return this.getDetails();
As a side note, in order to avoid having all of those (which gets pretty unmaintainable):
this.getNames = this.getNames.bind(this);
this.getPictures = this.getPictures.bind(this);
this.getMails = this.getMails.bind(this);
this.getDetails = this.getDetails.bind(this);
You can just define them as class properties like so:
getNames = () => {
// do stuff
}

React Axios API call with array loop giving wrong order?

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);
}
}

AsyncStorage.getItem returning promise [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
So I am trying to get data with AsyncStorage.getItem and then pass it to a function in React-native. but when I do that I get this error "data.filter is not a function" from my function. I think that the problem could be that I am not getting the data but insted a promise.
Constructor:
constructor(props) {
super(props)
const getSectionData = (dataBlob, sectionId) => dataBlob[sectionId];
const getRowData = (dataBlob, sectionId, rowId) => dataBlob[`${rowId}`];
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
sectionHeaderHasChanged : (s1, s2) => s1 !== s2,
getSectionData,
getRowData,
});
let data = AsyncStorage.getItem('connections').then((token) => {
token = JSON.parse(token);
return token;
});
const {dataBlob, sectionIds, rowIds} = this.formatData(data);
// Init state
this.state = {
dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds, rowIds),
left: true,
center: false,
right: false
}
}
Function:
formatData(data) {
// We're sorting by alphabetically so we need the alphabet
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
// Need somewhere to store our data
const dataBlob = {};
const sectionIds = [];
const rowIds = [];
// Each section is going to represent a letter in the alphabet so we loop over the alphabet
for (let sectionId = 0; sectionId < alphabet.length; sectionId++) {
// Get the character we're currently looking for
const currentChar = alphabet[sectionId];
// Get users whose first name starts with the current letter
const users = data.filter((user) => user.nickname.toUpperCase().indexOf(currentChar) === 0);
// If there are any users who have a first name starting with the current letter then we'll
// add a new section otherwise we just skip over it
if (users.length > 0) {
// Add a section id to our array so the listview knows that we've got a new section
sectionIds.push(sectionId);
// Store any data we would want to display in the section header. In our case we want to show
// the current character
dataBlob[sectionId] = { character: currentChar };
// Setup a new array that we can store the row ids for this section
rowIds.push([]);
// Loop over the valid users for this section
for (let i = 0; i < users.length; i++) {
// Create a unique row id for the data blob that the listview can use for reference
const rowId = `${sectionId}:${i}`;
// Push the row id to the row ids array. This is what listview will reference to pull
// data from our data blob
rowIds[rowIds.length - 1].push(rowId);
// Store the data we care about for this row
dataBlob[rowId] = users[i];
}
}
}
return { dataBlob, sectionIds, rowIds };
}
So my question is once I have the promise what should I do with it in order to pass it to my function and make this line work const users = data.filter((user) => user.nickname.toUpperCase().indexOf(currentChar) === 0);?
Yea you're not waiting for the promise to resolve, you can either turn the outer function in an async function and await for data to be resolved or whatever you put in the .then gets ran after the promise resolves. Also moving into componentDidMount. You might also want to look into FlatList instead of ListView:
componentDidMount(){
AsyncStorage.getItem('connections').then((token) => {
const token = JSON.parse(token);
const {dataBlob, sectionIds, rowIds} = this.formatData(token);
// Update State
this.setState({
dataSource: ds.cloneWithRowsAndSections(dataBlob, sectionIds, rowIds)
});
});
}

Continue on Null Value of Result (Nodejs, Puppeteer)

I'm just starting to play around with Puppeteer (Headless Chrome) and Nodejs. I'm scraping some test sites, and things work great when all the values are present, but if the value is missing I get an error like:
Cannot read property 'src' of null (so in the code below, the first two passes might have all values, but the third pass, there is no picture, so it just errors out).
Before I was using if(!picture) continue; but I think it's not working now because of the for loop.
Any help would be greatly appreciated, thanks!
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let article = document.querySelector('.c-entry-content').innerText;
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
let source = "The Verge";
let categories = "Tech";
if (!picture)
continue; //throws error
return {
title,
article,
picture,
source,
categories
}
});
}
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
If there is no picture, then document.querySelector() returns null, which does not have a src property. You need to check that your query found an element before trying to read the src property.
Moving the null-check to the top of the function has the added benefit of saving unnecessary calculations when you are just going to bail out anyway.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
const pictureElement = document.querySelector('.c-picture img');
if (!pictureElement) return null;
const picture = pictureElement.src;
const title = document.querySelector('h1').innerText;
const article = document.querySelector('.c-entry-content').innerText;
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
if (!result) continue;
// ... do stuff with result
}
Answering comment question: "Is there a way just to skip anything blank, and return the rest?"
Yes. You just need to check the existence of each element that could be missing before trying to read a property off of it. In this case we can omit the early return since you're always interested in all the results.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
const result = await page.evaluate(() => {
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1');
const content = document.querySelector('.c-entry-content');
const picture = img ? img.src : '';
const title = h1 ? h1.innerText : '';
const article = content ? content.innerText : '';
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
// ...
}
}
Further thoughts
Since I'm still on this question, let me take this one step further, and refactor it a bit with some higher level techniques you might be interested in. Not sure if this is exactly what you are after, but it should give you some ideas about writing more maintainable code.
// Generic reusable helper to return an object property
// if object exists and has property, else a default value
//
// This is a curried function accepting one argument at a
// time and capturing each parameter in a closure.
//
const maybeGetProp = default => key => object =>
(object && object.hasOwnProperty(key)) ? object.key : default
// Pass in empty string as the default value
//
const getPropOrEmptyString = maybeGetProp('')
// Apply the second parameter, the property name, making 2
// slightly different functions which have a default value
// and a property name pre-loaded. Both functions only need
// an object passed in to return either the property if it
// exists or an empty string.
//
const maybeText = getPropOrEmptyString('innerText')
const maybeSrc = getPropOrEmptyString('src')
async function scrape3() {
// ...
// The _ parameter name is acknowledging that we expect a
// an argument passed in but saying we plan to ignore it.
//
const evaluate = _ => page.evaluate(() => {
// Attempt to retrieve the desired elements
//
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1')
const content = document.querySelector('.c-entry-content')
// Return the results, with empty string in
// place of any missing properties.
//
return {
title: maybeText(h1),
article: maybeText(article),
picture: maybeSrc(img),
source: 'The Verge',
categories: 'Tech'
}
}))
// Start with an empty array of length 3
//
const evaluations = Array(3).fill()
// Then map over that array ignoring the undefined
// input and return a promise for a page evaluation
//
.map(evaluate)
// All 3 scrapes are occuring concurrently. We'll
// wait for all of them to finish.
//
const results = await Promise.all(evaluations)
// Now we have an array of results, so we can
// continue using array methods to iterate over them
// or otherwise manipulate or transform them
//
results
.filter(result => result.title && result.picture)
.forEach(result => {
//
// Do something with each result
//
})
}
Try-catch worked for me:
try {
if (await page.$eval('element')!==null) {
const name = await page.$eval('element')
}
}catch(error){
name = ''
}

Categories