check promise race condition in React functional component

  javascript, promise, reactjs

I have an INPUT NUMBER box that drives a promise. The promise result is shown in a nearby DIV. The user can click very fast to drive the number up and down, and in combination with promises that resolve in different times, this can create race conditions.

I would like to keep the DIV always consistent with the INPUT, that is, the DIV should either show nothing ("wait…") if the promise has not resolved yet, or the promise result for the value in the INPUT box.

To illustrate this use case I wrote this promise utility

function getPromise(i){
     return new Promise(resolve=>setTimeout(resolve.bind(null,i), 3000-i*300));
}

That is, the promise will always resolve slower for argument 1 than for 2, and argument 2 resolves slower than 3 etc.

In a React class component (or a Vue stateful component) the goal of keeping the INPUT and the DIV consistent can be achieved as follows:

class Test1 extends React.Component{
  constructor(){
     super();
     this.state={val:1, promiseResult:null};
     this.startPromise(1);
  }
  startPromise(x){
     this.setState({val:x, promiseResult:null});
     getPromise(x).then(data=> x===this.state.val && this.setState({promiseResult:data}));
  }
  render(){
      return React.createElement("div",{},
               React.createElement("input", {
                   type:"number" , min:1, max:10, value:this.state.val,
                   onChange:e=>this.startPromise(e.target.value)
               }),
               React.createElement("div", {}, this.state.promiseResult || "wait...")
              );
      }
}

The crux is the check x===this.state.val && that is, I check whether the promise parameter this.state.val is still the same as the one at the time of promise start. It could have been changed by rapid user clicks.

Question is there any way to achieve this with React functional components? In the following implementation, it would not be possible for the effect (or any other callback, such as an event listener) to access the current component state because it would do that through a closure, which will always return the val value from the first component render, which is undefined

function Test(){
   const [val, setValue]=React.useState(1);
   const [promiseResult, setPromiseResult]=React.useState();
   React.useEffect(function(){
      setPromiseResult(null);
      getPromise(val).then(x=> /* cannot check the current component state here! */
                               setPromiseResult(x));
   },[val]);

   return React.createElement("div",{},
               React.createElement("input", {
                     type:"number" , min:1, max:10, value:val, 
                     onChange:e=>setValue(e.target.value)
               }),
               React.createElement("div", {}, promiseResult ||"wait...")
          );
 }

Source: Ask Javascript Questions

LEAVE A COMMENT