0

I am building an e-commerce site with ReactJS and Commerce.js. I have a checkout context provider which is responsible for storing all state and functionality related to the checkout and order data. Below is the basic shape of the checkout state being stored in the context:

const initialState = {
  checkout_token: {
    id: "123456789"
  },
  order_data: {
    fulfillment: {
      shipping_options: [],
      shipping_countries: {},
      shipping_subdivisions: {},
      shipping_option: "",
    },
  }
}

I need to update the fulfillment section of my checkout data. This requires an API request to commerce.js using the checkout_token.id chained with a method call that updates the relevant part of checkout state. Here is one of the 3 methods I use to do this. You can assume the dispatch method works:

const setShippingCountries = (tokenId) => {
    commerce.services
      .localeListShippingCountries(tokenId)
      .then((countries) => {
        dispatch({
          type: ACTIONS.SET_SHIPPING_COUNTRIES,
          countries,
        });
      })
  };

Anytime that the checkout token is changed, I need to update the fulfillment data. I tried doing this with the following useEffect() hook. Note that isMounted is a ref used to stop the effect on mount:

useEffect(() => {
    if (isMounted.current && Object.keys(state.checkout_token).length > 0) {
      setShippingCountries(state.checkout_token.id);
      setSubdivisions(state.order_data.fulfillment.shipping_countries[0]);
      setShippingOptions(state...shipping_countries[0], 
        state...shipping_subdivisions[0]); //Forgive my abbreviation :)
    } else {
      isMounted.current = true; 
    }
  }, [state.checkout_token.id]);

Here is my error: TypeError: Cannot read properties of null (reading 'id')

This is caused because each method references data that was set in the line before. But the data is not updated when I reach the next line. I have tried turning them into Promises and chaining them but I was unable to get it right. I also tried using async...await but couldn't figure that out either. What is the best way to change these methods so that they are called synchronously?

1 Answer 1

1

async await version would look like this

 useEffect(() => {
        const f = async () => {
            await setShippingCountries(state.checkout_token.id);
            await setSubdivisions(state.order_data.fulfillment.shipping_countries[0]);
            await setShippingOptions(state.shipping_countries[0]);
        }
        if (isMounted.current && Object.keys(state.checkout_token).length > 0) {
            f()
        } else {
            isMounted.current = true;
        }
    }, [state.checkout_token.id]);
Sign up to request clarification or add additional context in comments.

3 Comments

thanks for the response! When I use this, it says that "await has no effect on this type of expression." and I still get an error. Is that because the 3 functions themselves are not asynchronous?
Indeed, they need to return a promise. Note that set states are asynchronous by design, so what you are trying to do goes against the react way. What you should do instead is only set the state once, and get the needed values from the previous functions synchronously const a = await f1(); const b = await f2(a); const c = await f3(b); setState(c);
Ok that helps. I ended up using 3 separate useEffect() hooks where each hook depends on the state of the variable in the previous hook. So once the countries are set, the subdivision api call is triggered, etc. Though your answer isn't exactly how I implemented it, it led me to a working solution. Thanks for the help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.