skip to Main Content

I am trying to get productInfo by productId from backend. I am iterating over productId and fetching data with axios. But it only iterates one element for every compile. How can i iterate all of them in one execution ?

const useBasket = (location) => {
    const [products, setProducts] = useState([]);
    const [descriptions, setDescriptions] = useState([]); 
    console.log("ACCOUNT ID:", location.location.state.accountId);
    useEffect(() => {
        const getBasket = async () => {
            const basketResponse = await Axios.get("http://localhost:8084/accountId/" + location.location.state.accountId);
            setProducts(basketResponse.data.products);
        };
        const getProductInfo = async (props) => {
            console.log("INSIDE getProductInfo:",props);
            const productResponse = await Axios.get("http://localhost:8081/product/" + props).catch(err => console.log(err));
            setDescriptions([...descriptions,productResponse.data.description]);  
        }
        getBasket();
        for(let i = 0; i < products.length; i++){
            console.log("INSIDE FOR:",products[i]);
            getProductInfo(products[i]);
        } 
        
    }, []); 
    console.log("DESCRIPTIONS:",descriptions);
    return { descriptions }; 
};

I tried to put the for loop inside of a async function. After that i made await the function call inside of the for loop.

I also put loading section when data is not complete it won’t return.

But none of them worked.

3

Answers


  1. State updates are asynchronous and the closure captures the current value of each of your state variables (which won’t change inside the same closure). You should directly access the values from the response instead and use await to ensure the request for products finishes before getting the descriptions.

    Here is one way to implement it:

    const getBasket = async () => {
        const basketResponse = await Axios.get("http://localhost:8084/accountId/" + location.location.state.accountId);
        return basketResponse.data.products;
    };
    const getProductInfo = (props) => {
        return Axios.get("http://localhost:8081/product/" + props).then(r => r.data.description);
    }
    (async () => {
        const newProducts = await getBasket();
        setProducts(newProducts);
        const newDescriptions = await Promise.all(newProducts.map(getProductInfo));
        setDescriptions([...descriptions, ...newDescriptions]);
    })();
    
    Login or Signup to reply.
  2. getBasket is an async function. Execution does not wait for it to finish/return before continuing. This means your for loop is referencing a products state variable this is unlikely to have been updated.

    There are a number of ways around this (assuming you cannot simply modify the product fetch to include descriptions):

    • You could wrap getBasket in a promise and await it before iterating.

    • You could combine your getProductInfo function within getBasket and iterate basketResponse.data.products

    • You could wrap getProductInfo in its own useEffect which is executed when products are updated i.e.

      useEffect(() => {
      
         const getProductInfo = async (props) => {
            const productResponse = await Axios.get("http://localhost:8081/product/" + props).catch(err => console.log(err));
            setDescriptions((prev) => [...prev, productResponse.data.description]);  
        }
      
        for(let i = 0; i < products.length; i++){
            getProductInfo(products[i]);
        }
      }, [products]);
      
    Login or Signup to reply.
  3. There are a few things that can be changed here, and they all boil down to how to handle stuff that doesn’t happen immediately, like:

    1. calling async functions that send api requests (you do wait for axios to return the value, but you don’t wait for the whole async function to finish, so then the for loop part starts executing before the request is finished)
    2. using state after setState (this isn’t instantaneous, setState calls are batched and executed together, it’s so quick that the only time you notice it when you write code that uses the state after a setState call)

    Now, there might be different ways to solve this, but here’s the solution with least changes:

    const useBasket = (location) => {
        const [products, setProducts] = useState([]);
        const [descriptions, setDescriptions] = useState([]);
        console.log("ACCOUNT ID:", location.location.state.accountId);
        
        const getBasket = async () => {
            const basketResponse = await Axios.get("http://localhost:8084/accountId/" + location.location.state.accountId);
            setProducts(basketResponse.data.products);
            return basketResponse.data.products;
        };
        const getProductInfo = async (props) => {
            console.log("INSIDE getProductInfo:", props);
            const productResponse = await Axios.get("http://localhost:8081/product/" + props).catch(err => console.log(err));
            setDescriptions([...descriptions, productResponse.data.description]);
        }
    
        useEffect(async () => {
            const prods = await getBasket();
            for (let i = 0; i < prods.length; i++) {
                console.log("INSIDE FOR:", prods[i]);
                getProductInfo(prods[i]);
            }
    
        }, []);
    
        console.log("DESCRIPTIONS:", descriptions);
        return { descriptions };
    };
    
    1. the getBasket and getProductInfo were moved outside the useEffect (optional, for readability)
    2. getBasket also returns the data when it is done
    3. an await was added before the getBasket call
    4. the for loop uses the returned list instead of the one from state
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search