In my react function component I send multiple server request and update a state hook value through asynchronous promises by appending server results to state most recent value, but once promise is created, value of state hook is not updated within the running promise, so if another promise updates state value, other running promises don’t get informed and as a result they use an older version of state for further state updates.
below code is a simplified version of my component, I expect to see same (and most updated) value from console log of line 19 from each promise while multiple promises are running and state get updated.
function App() { const [myArray, setMyArray] = useState([0,1,2]) const sleep = (ms:number) => { return new Promise(resolve => setTimeout(resolve, ms)); } const updateArray = () => { setMyArray([...myArray, myArray.length]) } const firePromise = () => { new Promise(async (resolve) => { const timeStamp = new Date().getTime() let repeatTime = 0 while(repeatTime < 12){ console.log("array: ", myArray, "promiseIdenifier: ", timeStamp); repeatTime += 1 await sleep(1000) } resolve({timeStamp, myArray}) }).then(val => { console.log("resolved: ", val); }).catch(err => { console.log("rejected: ", err); }) } return ( <div className="App"> <button onClick={firePromise}>new promise</button> <button onClick={updateArray}>updateArray</button> </div> ); } export default App;
Advertisement
Answer
once React component is rendered, it can be assumed that the component’s current “state” exists like a snapshot.
“myArray” in console.log is “myArray” at the time firePromise was created. so it is correct to keep the first value. ( Each time the component is rendered, a new firePromise is created. )
There is a way. The first is to use a ref, and the second is to use the setState.
First
function App() { const myArray = useRef<Array<number>>([0, 1, 2]); const sleep = (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; const updateArray = () => { myArray.current.push(myArray.current.length); }; const firePromise = () => { new Promise(async (resolve) => { const timeStamp = new Date().getTime(); let repeatTime = 0; while (repeatTime < 12) { console.log( "array: ", myArray.current, "promiseIdenifier: ", timeStamp ); repeatTime += 1; await sleep(1000); } resolve({ timeStamp, myArray: myArray.current }); }) .then((val) => { console.log("resolved: ", val); }) .catch((err) => { console.log("rejected: ", err); }); }; return ( <div className="App"> <button onClick={firePromise}>new promise</button> <button onClick={updateArray}>updateArray</button> </div> ); } export default App;
Second
function App() { const [myArray, setMyArray] = useState([0, 1, 2]); const sleep = (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; const updateArray = () => { setMyArray([...myArray, myArray.length]); }; const firePromise = () => { new Promise(async (resolve) => { const timeStamp = new Date().getTime(); let repeatTime = 0; while (repeatTime < 12) { setMyArray((prevMyArray) => { console.log("array: ", prevMyArray, "promiseIdenifier: ", timeStamp); return prevMyArray; }); repeatTime += 1; await sleep(1000); } setMyArray((prevMyArray) => { resolve({ timeStamp, prevMyArray }); return prevMyArray; }); }) .then((val) => { console.log("resolved: ", val); }) .catch((err) => { console.log("rejected: ", err); }); }; return ( <div className="App"> <button onClick={firePromise}>new promise</button> <button onClick={updateArray}>updateArray</button> </div> ); } export default App;
When passing a callback to the setState function, the current state is passed as the first argument. this is a shortcut using this.
It is recommended to use the value that the view should change when the value changes as the state. Changing “myArray ” does not affect the view, so using ref is the correct method.
read this : https://iqkui.com/a-complete-guide-to-useeffect/