简体   繁体   中英

React SetState Race Condition using Promise.all

componentDidMount() {
    Promise.all([OfferCreationActions.resetOffer()]).then( () => {
        OfferCreationActions.updateOfferCreation('viewOnly', true);
        OfferCreationActions.updateOfferCreation('loadingOffer', true);
        Promise.all([
            OfferCreationActions.loadBarcodes(),
            OfferCreationActions.loadBrandBarcodes(),
            OfferCreationActions.loadBrands(),
            OfferCreationActions.loadPayers(),
            OfferCreationActions.loadSegments(),
            OfferCreationActions.loadTactics(),
            OfferCreationActions.loadStates(),
        ]).then(() => {
            // let state settle before loading
            setTimeout(() => {
                OfferCreationActions.loadOffer(this.props.match.params.offerId);
            }, 1500);
        });
    });
}

I'm working on a React app that needs to preload some data into state then load a larger object that references the preloaded data to map some fields. I've ran into a race condition in which some of the data from the promise chain is still being processed when I try to do the mapping. I added in the timeout yesterday but that doesn't feel like the best solution to me. I'm still fairly new to React and we are using Reflux as the store (if that makes a difference). Is there a better way to ensure all the data from the promise is currently being reflected in the state prior to making the call? Should I hook into componentShouldUpdate and check all of the fields individually?

There is a fundamental flaw with the way this is implemented! You are breaking the principle of uni directional data flow. Here are a few suggestions to fix it.

  1. Do your side effect handling inside a seperate overarching function.

Handling promise race condition is a side effect(Something that is outside of React's UniFlow). So this is not a problem linked to "React". So as the first step onComponentDidMount delegate this race condition logic to a seperate action. Possibly do it inside "resetOfferOverall()" which is actually what is happening I guess.

  1. Manage the promise inside the action and dispatch payloads to the store

In your code you are guaranteed that the "then" will be executed after the promise is resolved. Howerver the 2 calls to "updateOfferCreation" do not fall under this contract since it's outside the promise.all. May be they also need to come inside the massive promise.all section? Maybe they need to be completed before running the massive section. Just recheck this!

resetOfferOverall() {
    Promise.all([OfferCreationActions.resetOffer()]).then( () => {
    .then( () => {

        // These are not guaranteed to be completed before the next "then" section!
        OfferCreationActions.updateOfferCreation('viewOnly', true);
        OfferCreationActions.updateOfferCreation('loadingOffer', true);
        //*****************************************

        Promise.all([
            OfferCreationActions.loadBarcodes(),
            OfferCreationActions.loadBrandBarcodes(),
            OfferCreationActions.loadBrands(),
            OfferCreationActions.loadPayers(),
            OfferCreationActions.loadSegments(),
            OfferCreationActions.loadTactics(),
            OfferCreationActions.loadStates(),
        ]).then(() => {               
            OfferCreationActions.loadOffer(offerId);
        });
    });
}

If you want this sections to be completed before getting into that massive promise all, change your code as follows.

async resetOfferOverall() {
    Promise.all([OfferCreationActions.resetOffer()]).then( () => {
    .then( () => {

        await OfferCreationActions.updateOfferCreation('viewOnly', true);
        await  OfferCreationActions.updateOfferCreation('loadingOffer', true);
        //await will stop code execution until the above async code is completed

        Promise.all([
            OfferCreationActions.loadBarcodes(),
            OfferCreationActions.loadBrandBarcodes(),
            OfferCreationActions.loadBrands(),
            OfferCreationActions.loadPayers(),
            OfferCreationActions.loadSegments(),
            OfferCreationActions.loadTactics(),
            OfferCreationActions.loadStates(),
        ]).then(() => {   
            //Now JS Guarantees that this call will not be called until everything above has been resolved!            
            OfferCreationActions.loadOffer(offerId);
        });
    });
}
  1. Make sure that the actions you are waiting are returning a promise

Whatever pomises you wait on, if you do not actually return the relevant promise that is within the call itself, your code will not work properly.Let's consider the load barcodes action and Let's assume you use axios to fetch data.

loadBarcodes(){
    // This "return" right here is vital to get your promises to behave properly
    return axios.get('https://localhost:8080/api/barcodes/').then((response) =>{
        //DISPATCH_TO_STORE
    });

    //If you did not return this promise this call will resolve immediately

}
  1. On your component watch for the relevent Store. Show a loader until the payload is loaded to the store.

As you can see by relying on a store update to show the data we do not break the unidirectional data flow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM