Home > Software design >  Component not re-rendering when state is changed
Component not re-rendering when state is changed

Time:03-01

I am trying to use update state in a react function component but it is not rerendering right away.

My application takes an input for material cost of each product and calculates the total of that product. I would like the component to be rerendered to show the updated total after the input is updated. It seems that the total is calculated and the state is updating every time the material cost is changed, but I can't get the displayed total to say the new amount.

Here's a screenshot of my application (some code for elements in the table were removed for simplicity)

enter image description here This is what the data looks like. In this example I am saving the first element of the array.

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,
            },
        ],
    }

This is the parent component. It has the main render and the setTotal and setMaterial functions. The render maps through each of the productData.products so the child component can render them.

The setTotal and setMaterial functions are almost identical (I'm sure there's a way to refactor that). This code seems to work well to update state, although I do notice that doing console.log() within those functions isn't working.


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;

This is the child element. It handles each product individually.

The first calcTotal() is used to set the original total. This call works, but the total doesn't continue to rerender when the material is changed later.

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;

CodePudding user response:

It doesn't work because both setMaterial and setTotal have an inner function , but the inner function is never executed.

You should remove the functions setTotalUpdate and setMaterialUpdate to resolve your update problem, as below.

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;

CodePudding user response:

Rusty on react, but I believe you have to call setState somewhere after the changes are applied.

  • Related