反应useEffect循环

时间:2020-10-11 15:17:35

标签: reactjs

我正在实现一个React购物车,并且我有一个useEffect钩子存在锁定循环的问题。

最初,我使用useEffect来获取购物车中的ID和产品数量,并将这些值存储在名为 shopCart 的对象的状态变量数组中:

// fetches the id's and quantities of all products in the user's shopping cart
    // and assigns these values to the shopCart array
    // Each value of the shopCart array should be an object like this: {id:random_id, quantity:number_of_products }]
    useEffect(() => {
        let auxCart = []; // auxiliary array that will receive the result of the fetch request
        // postData: performs a POST request with {userId:userId} as the body of the request
        postData('REQUEST1_URL', { userId: userId })
            .then(data => {
                for (let i = 0; i < data.shopCart.length; i++) {
                    auxCart[i] = {};
                    auxCart[i].id = data.shopCart[i].id; //this shopCart is not the same shopCart state variable. Just a value received from the database
                    auxCart[i].quantity = data.shopCart[i].quantity;
                }
            });
        setShopCart(auxCart);
    }, [])

此后,我通过添加带有产品信息的属性/键(例如产品标题,价格和图像)来修改 shopCart

   // Modifies shopCart, which should contain only id's and quantities at the moment, 
    //  assigning details to each product such as price and title.
    // Each value of shopCart should be an object like this:
    // {id:random_id, quantity:number_of_products,
    // title:product_name, price:product_price,
    // oldPrice:old_product_price, image:array_of_images,
    // type:type_of_product}
    useEffect(() => {
        console.log('useEffect2');
        if (!isFirstRender) { //Ref hook variable used to test if the useEffect is in the first render or not 
            let aux = [...shopCart]; // creates a copy of shopCart
            for (let i = 0; i < shopCart.length; i++) {
                // fetches the details of each product
                fetch('REQUEST2_URL').
                    then(res => res.json()).
                    then(res => {
                        aux[i].image = res[0].image;
                        aux[i].title = res[0].title;
                        aux[i].price = res[0].price;
                        aux[i].oldPrice = res[0].oldPrice;
                        aux[i].type = res[0].type;
                    });
            }
            setShopCart(aux);
        }
    }, [shopCart]);

问题是第二个useEffect多次打印'useEffect2',表明它处于锁定循环中。我该如何解决?我期待的是,由于获取的产品信息不会更改,因此第二个useEffect不会每次都重复,但确实会重复。我将 shopCart 用于其依赖数组中,以便等待以完成更新 shopCart 的第一个请求。

2 个答案:

答案 0 :(得分:1)

之所以发生这种情况,是因为在您的第二个useEffect中,您的依赖项数组中有shopCart,并且在其中设置了shopCart的函数中,因此它无限运行。

您可以在第一个useEffect内完成所有用例。怎么样?您可以await进行第一个fetch响应,然后再响应第二个请求。 (如果需要,可以保留then(),但IMO async...await的方法要干净得多)

让它像这样:

useEffect(async () => {
        let auxCart = []; // auxiliary array that will receive the result of the fetch request
        // postData: performs a POST request with {userId:userId} as the body of the request
        const data = await postData('REQUEST1_URL', { userId: userId })
                for (let i = 0; i < data.shopCart.length; i++) {
                    auxCart[i] = {};
                    auxCart[i].id = data.shopCart[i].id; //this shopCart is not the same shopCart state variable. Just a value received from the database
                    auxCart[i].quantity = data.shopCart[i].quantity;
                }
            let aux = [...shopCart]; // creates a copy of shopCart
            for (let i = 0; i < shopCart.length; i++) {
                // fetches the details of each product
                const res = await fetch('REQUEST2_URL').
                const resData = await res.json();
                        aux[i].image = resData[0].image;
                        aux[i].title = resData[0].title;
                        aux[i].price = resData[0].price;
                        aux[i].oldPrice = resData[0].oldPrice;
                        aux[i].type = resData[0].type;
            }
       // do whatever you want with those objects
    }, [])

为了使代码更简洁,您还可以摆脱那些aux变量(似乎很像命令式编程语言),并开始使用mapfilter等数组函数。根据您的需要。确实,在第一个for loop中,您只是在可以直接使用该数组时将相同的值设置给另一个数组:

useEffect(async () => {
        const shopCart = await postData('REQUEST1_URL', { userId: userId }).shopCart
            let aux = [...shopCart]; // creates a copy of shopCart
            for (let i = 0; i < shopCart.length; i++) {
                // fetches the details of each product
                const res = await fetch('REQUEST2_URL').
                const resData = await res.json();
                        aux[i].image = resData[0].image;
                        aux[i].title = resData[0].title;
                        aux[i].price = resData[0].price;
                        aux[i].oldPrice = resData[0].oldPrice;
                        aux[i].type = resData[0].type;
            }
       // do whatever you want with those objects
    }, [])

在第二个for..loop中,您也可以使用map,但这有点复杂,因为您应该在async内使用map函数,然后等待所有Promises来解决使用Promise.all()的问题,这只是一个全新的解决方案...留给您;)

答案 1 :(得分:1)

在代码中创建无限循环有两件事。通过将这两种东西组合,它创建了循环。

let aux = [...shopCart]; // creates a copy of shopCart

<=此操作会创建一个普通的 new 对象,因此始终会创建一个 new shopCart,即使当内容可能相同

setShopCart(aux);
    }
}, [shopCart]);

<=您正在将此新对象设置为shopCart,并且由于React始终认为这是一个 new 对象,因此它总是重新运行useEffect

如何解决此问题?

我不确定为什么将您分成两个不同的useEffect并在第二个shopCart中添加useEffect作为依赖项,但是根据您的描述,您始终可以将它们组合在一起到一个useEffect中,然后使用 Promise async / await 来确保仅在第一个API调用完成后才运行第二个API调用。有关更多详细信息,我认为Agustin发布了一个很好的示例。