状态更改时组件不重新渲染

Component not re-rendering when state is changed

我正在尝试在 React 函数组件中使用更新状态,但它不会立即重新渲染。

我的应用程序输入每个产品的 material 成本并计算该产品的总和。我希望在更新输入后重新呈现组件以显示更新后的总数。每次 material 成本更改时,似乎总计已计算并且状态正在更新,但我无法让显示的总计说出新金额。

这是我的应用程序的屏幕截图(为简单起见,删除了 table 中元素的一些代码)

这就是数据的样子。在这个例子中,我保存了数组的第一个元素。

export let data = 
    {
        name: "Name",
        description:
            "",
        products: [
            {
                id: 1,
                name: "Name 1",
                material: 1.05,
                time: 25,
                total: 0,
            },
            {
                id: 2,
                name: "Name 2",
                material: 3,
                time: 252,
                total: 0,
            },
        ],
    }

这是父组件。它具有主要的渲染功能以及 setTotal 和 setMaterial 功能。通过每个 productData.products 渲染映射,以便子组件可以渲染它们。

setTotal 和 setMaterial 函数几乎相同(我确信有一种方法可以重构它)。这段代码似乎可以很好地更新状态,尽管我确实注意到在这些函数中执行 console.log() 是行不通的。


function CompareCard({ type, labor }) {

    const [productData, setProductData] = useState(data);
const setTotal = () => {
        function setTotalUpdate(id, total) {
            const productPrevious = productData.products.find(function (rec) {
                return rec.id === id;
            });

            const productUpdated = {
                ...productPrevious,
                total: total,
            };

            const productNew = productData.products.map(function (rec) {
                return rec.id === id ? productUpdated : rec;
            });

            const productFullNew = {
                ...productData,
                products: productNew,
            };

            setProductData(productFullNew);
        }
    };

    const setMaterial = () => {
        function setMaterialUpdate(id, materialCost) {
            const productPrevious = productData.products.find(function (rec) {
                return rec.id === id;
            });

            const productUpdated = {
                ...productPrevious,
                material: materialCost,
            };

            const productNew = productData.products.map(function (rec) {
                return rec.id === id ? productUpdated : rec;
            });

            console.log("productsNew:", productNew);

            const productFullNew = {
                ...productData,
                products: productNew,
            };
            setProductData(productFullNew);
        }
    };

    return (
        <div className="Card">
            <h3> {productData.name} </h3>
            <p>{productData.description}</p>
            <table className="table">
                <thead>
                    <tr>
                        
                        <th scope="col">
                            <p>
                                <b> Material </b>
                            </p>
                        </th>
                        
                        <th scope="col">
                            <p>
                                <b> Labor </b>
                            </p>
                        </th>
                        <th scope="col">
                            <p>
                                <b> Total</b>
                            </p>
                        </th>
                    </tr>
                </thead>
                <tbody id={`${productData.name}`}>
                    {productData.products.map((product) => {
                        return (
                            <Products
                                key={product.id}
                                product={product}
                                labor={3}
                                setMaterial={setMaterial}
                                setTotal={setTotal}
                            />
                        );
                    })}
                </tbody>
            </table>
        </div>
    );
}

export default CompareCard;

这是子元素。它单独处理每个产品。

第一个calcTotal()用于设置原始总数。此调用有效,但当 material 稍后更改时,总计不会继续重新呈现。

import React, { useState, useEffect } from "react";
function Products({ product, labor, setMaterial, setTotal }) {
    function setMaterialState(newMaterial) {
        product.material = newMaterial;
    }

    function calcTotal() {
        product.total = labor + product.material;
    }
    

    if (product.total === 0) {
        calcTotal();
    }

    useEffect(() => {
        setTotal(product.id, product.total);
    }, [product.total]);

    useEffect(() => {
        setMaterial(product.id, product.material);

        //setTotal(product.id, product.material + labor);
    }, [product.material]);

    function ProductMaterial() {
        const [name, setName] = useState("");

        function handleChange(e) {
            setName(parseFloat(e.target.value));
            setMaterialState(parseFloat(e.target.value));
            setMaterial(product.id, product.material);
            calcTotal();
            setTotal(product.id, product.total);
        }

        return (
            <input
                type="number"
                name="firstName"
                onChange={handleChange}
                value={product.material}
            />
        );
    }

    return (
        <tr key={product.name}>
            
            <td>
                <ProductMaterial product={product} />{" "}
    
            <td id={`total-${product.id}`}>{product.total}</td>
        </tr>
    );
}

export default Products;

它不起作用,因为 setMaterial 和 setTotal 都有一个内部函数,但内部函数永远不会执行。

您应该删除函数 setTotalUpdate 和 setMaterialUpdate 来解决您的更新问题,如下所示。

function CompareCard({ type, labor }) {

    const [productData, setProductData] = useState(data);
    const setTotal = (id, total) => {
            const productPrevious = productData.products.find(function (rec) {
                return rec.id === id;
            });

            const productUpdated = {
                ...productPrevious,
                total: total,
            };

            const productNew = productData.products.map(function (rec) {
                return rec.id === id ? productUpdated : rec;
            });

            const productFullNew = {
                ...productData,
                products: productNew,
            };

            setProductData(productFullNew);
    };

    const setMaterial = (id, materialCost) => {
            const productPrevious = productData.products.find(function (rec) {
                return rec.id === id;
            });

            const productUpdated = {
                ...productPrevious,
                material: materialCost,
            };

            const productNew = productData.products.map(function (rec) {
                return rec.id === id ? productUpdated : rec;
            });

            console.log("productsNew:", productNew);

            const productFullNew = {
                ...productData,
                products: productNew,
            };
            setProductData(productFullNew);
    };

    return (
        <div className="Card">
            <h3> {productData.name} </h3>
            <p>{productData.description}</p>
            <table className="table">
                <thead>
                    <tr>
                        
                        <th scope="col">
                            <p>
                                <b> Material </b>
                            </p>
                        </th>
                        
                        <th scope="col">
                            <p>
                                <b> Labor </b>
                            </p>
                        </th>
                        <th scope="col">
                            <p>
                                <b> Total</b>
                            </p>
                        </th>
                    </tr>
                </thead>
                <tbody id={`${productData.name}`}>
                    {productData.products.map((product) => {
                        return (
                            <Products
                                key={product.id}
                                product={product}
                                labor={3}
                                setMaterial={setMaterial}
                                setTotal={setTotal}
                            />
                        );
                    })}
                </tbody>
            </table>
        </div>
    );
}

export default CompareCard;