skip to Main Content

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

2

Answers


  1. 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);
    
    Login or Signup to reply.
  2. The issue you’re facing is related to how you’re updating the state inside the loop. Since React state updates are asynchronous, you might be experiencing unexpected behavior due to the loop running faster than the state updates. To fix this, you should accumulate the updated products in a separate array and then update the state once, outside of the loop.

    Here’s how you can modify your code to ensure proper updating of the UI after fetching and updating the product prices:

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

    In this code, I’ve created a new array updatedProducts where I accumulate the products with updated price information. After the loop, I update the state with this array using a single setProducts call. This ensures that the UI will be updated with all the correct price information once the state is updated.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search