I'm trying to make 200 calls to a remote resource to display in my table, while displaying a progress bar to show the number of calls remaining.
Use this example to demonstrate how to use Fetch()
and Promise.all()
to call setState() code> to update new data.
My problem is with each promise's .then()
, which calculates some logic and then calls setState()
to update the data.
My progress bar uses Object.keys(data).length
to show progress.
After Promise.all()
triggers the "Complete" state, removing the progress bar, the promises themselves are still calling their then()
, which causes the progress bar to be hidden before all resolved promises are displayed.
How to deal with this problem correctly?
Demo, use setTimeout()
to simulate expensive logic.
The problem is that Promise.all.then: 20
should be after Render 20
.
Render 0 ... Render 12 Promise.all.then: 20 # I need this to be recorded after each Render Render 13 ... Render 19 Render 20
To make the demo show the problem, the progress bar was removed (turned red) before it was completely filled.
const { useState } = React; const Example = () => { const [done, setDone] = useState(false); const [data, setData] = useState({}); const demoData = Array.from(Array(20).keys()); const demoResolver = (x) => new Promise(res => setTimeout(() => res(x), Math.random() * 1250)) const loadData = () => { const promises = demoData.map(c => demoResolver(c)); promises.forEach(promise => { promise .then(r => { setTimeout(() => { setData(p => ({ ...p, [r]: r })); }, 500); }) }); Promise.all(promises) .then(r => { console.log('Promise.all.then: ', r.length) setDone(true); }) } console.log('Render', Object.keys(data).length); const progressBarIsShownDebugColor = (done) ? 'is-danger' : 'is-info'; return () } ReactDOM.render( {'Example'}
, document.getElementById("react"));
.as-console-wrapper { max-height: 50px !important; }
The problem shown in the code above is that after getting the data, there is an additional 500ms async delay before setting the state. In the actual code, it sounds like there is additional processing (probably synchronous) that causes
setData
to be called after.all
.The best practice is to have
done
as a computed property rather than a separate state, because at that point you don't need to rely on the state to set up contention, andObject.keys( data).length
is cheap enough that it doesn't hurt performance (and you use it in other areas, you can cache it into a variable if it becomes a problem).