Reputation: 2226
My goal is to add a new stock - which consists of a quote, chart, meta info into their respective arrays in the component state, by calling my _addStock()
function in componentDidMount
. However, after looping through and calling _addStock
, I only have 1 stock within each respective array.
I can get this working by adding a delay via setTimeouts between each _addStock
call - but that defeats the purpose of promises.
I thought with async / awaits, JavaScript would "synchronously" execute code. However, it seems like the setState
is only being called once when I'm calling _addStock
multiple times.
What's going on?
_fetchMeta
, _fetchQuote
, ...etc return promises.
...
// this is what my state ends up looking like, only 1 per key when I'm expecting 4.
this.state = {
stocks: [
{
symbol: 'MSFT',
display: '...',
color: '...'
}
],
metas: [
{
symbol: 'MSFT',
name: '...',
description: '...'
}
],
quotes: [
{
symbol: 'MSFT',
price: '...',
change: '...'
}
],
charts: [
{
symbol: 'MSFT',
data: "..."
}
]
}
//...
_addStock = async symbol => {
const { metas, quotes, charts, stocks } = this.state;
// check if stock already exists
const hasStock = stocks.filter(s => s.symbol === symbol).length;
if (hasStock) {
return;
}
const meta = await this._fetchMeta(symbol);
const quote = await this._fetchQuote(symbol);
const chart = await this._fetchChart(symbol);
const stock = {
symbol: symbol.toUpperCase(),
color: setStockColor(),
display: true
};
this.setState({
...this.state,
metas: [...metas, meta],
quotes: [...quotes, quote],
charts: [...charts, chart],
stocks: [...stocks, stock]
});
};
//...
componentDidMount() {
const initStocks = ["FB", "MSFT", "NVDA", "AAPL"];
initStocks.forEach(s => this._addStock(s));
}
Upvotes: 0
Views: 2263
Reputation: 1061
When you call setState
multiple time in short interval react batch all setState
calls for performance and execute them collectively, that's why when to call setState
2nd time first request is still pending and component state
is empty at the end only one item is inserted.
You can use following approach to bring data asynchronously, and it will update state only once with final data. As it is not using state for each iteration that's why it will maintain data consistency too.
_addStock = symbols => {
const { metas, quotes, charts, stocks } = this.state;
// a variable to keep track how many symbols have been processed
let count = 0;
symbols.forEach(async symbol => {
// check if stock already exists
const hasStock = stocks.some(s => s.symbol === symbol);
if (!hasStock) {
//make all api requests in parallel
let meta = this._fetchMeta(symbol);
let quote = this._fetchQuote(symbol);
let chart = this._fetchChart(symbol);
let stock = {
symbol: symbol.toUpperCase(),
color: setStockColor(),
display: true
};
//wait for responses
meta = await meta;
quote = await quote;
chart = await chart;
//add all values to old state
metas.push(meta);
quotes.push(quote);
chart.push(chart);
stock.push(stock);
}
//update count
++count;
//if all values have been retrieved update state
if(count === symbols.length){
this.setState({
...this.state,
metas: [...metas],
quotes: [...quotes],
charts: [...charts],
stocks: [...stocks]
});
}
});
};
componentDidMount() {
const initStocks = ["FB", "MSFT", "NVDA", "AAPL"];
//Pass whole array to _addStock
this._addStock(initStocks);
}
Upvotes: 0
Reputation: 1478
For fetching multiple URLs in parallel using Promise.all. A roughly implement in your case would be:
_addStock = async symbol => {
...
let fetchMeta = this._fetchMeta.resolve(symbol);
let fetchQuote = this._fetchQuote.resolve(symbol);
let fetchMeta = this._fetchChart.resolve(symbol);
let data = await Promise.all([
fetchMeta,
fetchQuote,
fetchChart,
]).then((data) => {
console.log(data);
// Inspect your data here setState accordingly
this.setState({ ... });
});
};
Upvotes: 1
Reputation: 15292
calling _addStock
multiple time,call setState
multiple time.Its cause multiple setState
operation to run in parallel without waiting confirmation of previous setState update. As a setState
async operation, you get such weird behaviour.
To fix it,you can add one more level of async/await
with promise
_addStock = async symbol => {
return new Promise((resolve,reject)){
this.setState({
...this.state,
metas: [...metas, meta],
quotes: [...quotes, quote],
charts: [...charts, chart],
stocks: [...stocks, stock]
},()=>{
resolve(true); //give confirmation setState is updates successfully.
});
}
};
call _addStock
with await
.
componentDidMount() {
const initStocks = ["FB", "MSFT", "NVDA", "AAPL"];
initStocks.forEach( async s => {
await this._addStock(s)
});
}
Upvotes: 0