Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

UseState not updating correctly with an Await function inside iteration of an array

i have a little problem with updating the UI in React after fetching additional data from the server. The problem is that not all products are updating the price in the UI. I´m sure its because of the async function. Heres my code:

useEffect(() => {
    async function loadNotes() {
      console.log("loading notes");
      try {
        setShowNotesLoadingError(false);
        setNotesLoading(true);
        const products = await ProductsApi.fetchProducts();
        for (let index = 0; index < products.length; index++) {
          const prices = await ProductsApi.getLowestPrice(products[index].asin);
          products[index].lowestPriceFraction = prices.lastPriceFraction;
          products[index].lowestPriceWhole = prices.lastPriceWhole;
          setProducts(products);
        }
        setProducts(products);

      } catch (error) {
        console.error("Fehler: " + error);
        setShowNotesLoadingError(true);
      } finally {
        setNotesLoading(false);
      }
    }
    loadNotes();
  }, []);

Hope someone can help my to fix my little problem. Thank you

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

products[index].lowestPriceFraction = prices.lastPriceFraction;
products[index].lowestPriceWhole = prices.lastPriceWhole;

The problem is that these lines are mutating the array, and that array is in state (after the first loop anyway). Mutating state causes problems, because react expects it can to an === comparison between the old and new state to tell if it has changed. So when you call setProducts(products) react does its ===, sees that it’s the same array, and thus skips every render but the first.

Instead, every time you set state you must do so without mutation. In your case that means making a copy of the array, and making a copy of the specific item you want to change

const products = await ProductsApi.fetchProducts();
setProducts(products);
for (let index = 0; index < products.length; index++) {
  const prices = await ProductsApi.getLowestPrice(products[index].asin);
  // using function version of setProducts, so i have the latest state
  setProducts((prev) => {
    // Creating a new array
    return prev.map((val, i) => {
      if (index === i) {
        // Creating a new item
        return {
          ...val, 
          lowestPriceFraction: prices.lastPriceFraction,
          lowestPriceWhole: prices.lastPriceWhole
        }
      } else {
        return val;
      }
    });
  });
}

Btw, one way you could change this code is to get all the prices in parallel (thus reducing the total loading time) and just set state once at the end (thus simplifying the code):

const products = await ProductsApi.fetchProducts();
const promises = products.map(product => {
  return ProductsApi.getLowestPrice(product.asin)
});
const pricesArray = await Promise.all(promises);
pricesArray.forEach((prices, i) => {
  // I am mutating, but this data isn't in state yet so it won't hurt.
  products[i].lowestPriceFraction = prices.lastPriceFraction;
  products[i].lowestPriceWhole = prices.lastPriceWhole
});
setProducts(products);
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading