Home > Software engineering >  How to make function with a few setStates in it synchronous?
How to make function with a few setStates in it synchronous?

Time:03-06

I need to make a button click handler which have a few other function calls in it. One of them is a onAccept function which has a few setStates in it and want to wait until them all is done. Is there a way to make onAccept synchronous?

button click handler

const onUpdateBoundaries = async (recommendation) => {
    await getSnippetIndex(
        //some props
    ).then(response => {
      onAccept({...recommendation, index: response});
    });
    
    fetchRecommendations() //<- this function shouldn't be called until onAccept's setStates are done
  };

onAccept

const onAccept = (recommendation) => {
    setAccepted((accepted) => [
      ...new Set([...accepted, ...recommendation.cluster_indices.map(recommendation => recommendation.index)]),
    ]);
    setRejected((rejected) => [
       ...new Set(removeFromArray(rejected, recommendation.cluster_indices.map(recommendation => recommendation.index)))
    ]);
  };

fetchRecommendations

const fetchRecommendations = async () => {
    try {
      const {//some props
        propagated_accepted,
        propagated_rejected,
      } = await getRecommendations(
          //some props
      );
      setAccepted((accepted) => [...accepted, ...propagated_accepted]);
      setRejected((rejected) => [...rejected, ...propagated_rejected]);
    } catch (err) {
      //handling
    }
    setIsWaitingForRecommendations(false);
  };

CodePudding user response:

You can try with useEffect and useRef to achieve it

//track all previous values before state updates
const previousValues = useRef({ rejected, accepted });

useEffect(() => {
   //only call `fetchRecommendations` once both `rejected` and `accepted` get updated
   if(previousValues.current.rejected !== rejected && previousValues.current.accepted !== accepted) {
      fetchRecommendations()
   }
}, [rejected, accepted])

Another easier way that you can try setState, which is the old-school function with callback (the problem with this solution is you need to use class component - NOT function component)

const onAccept = (recommendation) => {
  setState((prevState) => ({
    accepted: [
      ...new Set([...prevState.accepted, ...recommendation.cluster_indices.map(recommendation => recommendation.index)]),
    ],
    rejected: [
      ...new Set(removeFromArray(prevState.rejected, recommendation.cluster_indices.map(recommendation => recommendation.index)))
    ]
  }), () => {
    //callback here
    fetchRecommendations()
  })
}

CodePudding user response:

React is declarative, which means it will control the setState function calls incl. batching them if necessary to optimise performance.

What you can do is make use of a useEffect to listen for changes in state and run code you need to run after state change there. For eg: ( I'm assuming your two states are accepted and rejected)

  useEffect(() => {
    fetchRecommendations() //<- gets called everytime accepted or rejected changes
   }, [accepted, rejected])

// onAccept remains the same

 //button click handler

const onUpdateBoundaries = async (recommendation) => {
 const response = await getSnippetIndex( //some props )
  onAccept({...recommendation, index: response});
};

If you want to run it only if current values of accepted or rejected has changed, you can make use of use Ref to store the previous values of accepted and rejected.

You can create a custom hook like

  function usePrevious(value) {
      const ref = useRef();
      useEffect(() => {
        ref.current = value;
      });
      return ref.current;
    }

Then

// import usePrevious hook  
const prevAccepted = usePrevious(accepted)
const prevRejected = usePrevious(rejected)

  useEffect(() => {
       if(prevAccepted!=accepted && prevRejected!=rejected)
        fetchRecommendations() //<- gets called everytime accepted or rejected changes
       }, [accepted, rejected])
    
    const onUpdateBoundaries = async (recommendation) => {
     const response = await getSnippetIndex( //some props )
      onAccept({...recommendation, index: response});
    };

Think something like this would do the trick. Let me know if this works :)

CodePudding user response:

you can make a async method like this

const SampleOfPromise = () => {
  onClick=async()=>{
    await myPromise();
  }
  
  const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('sample');
    }, 300);
  });
  return(
     <Button onClick={onClick}>

    </Button>
  )
}
  • Related