You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中在.map内调用setState引发无限循环的解决方法

解决showDiscounted函数引发的无限循环问题

问题根源

你在组件渲染流程中调用了showDiscounted函数,而该函数内部直接调用setTotal更新状态——组件渲染→触发showDiscounted执行→更新total状态→触发组件重新渲染→再次调用showDiscounted,形成闭环,导致无限循环。

最佳解决方案:使用派生状态(优先推荐)

由于total的值完全由parts、discount、option这些现有数据推导而来,不需要单独维护为状态。直接在渲染时计算total,既避免循环,又减少状态同步的麻烦。

修改步骤:

  1. 删除冗余的total和discountedTotal状态(后者在代码中未实际使用)
  2. 编写计算total的函数,在渲染时直接调用
  3. 修改showDiscounted,仅返回折扣后价格,移除内部状态更新逻辑

修改后的核心代码:

import { useState } from "react";

const QuoteCalcBot = ({ parts, setParts, option, discount }) => {

    //change individual discount for a part
    const handleDiscountChange = (part, e) => {
        console.log(e.target.value);
        console.log(part);
    }

    // 仅计算并返回单个零件的折扣后月付价格
    const showDiscounted = (price) => {
        const temp = Math.ceil((price * ((100 - discount) / 100)) / 36)
        return (
            <div className="col-2">
                {temp}
            </div>
        )
    }

    const addToTotal = (price) => {
        return Math.ceil(price / 36)
    }

    // 计算各列的折扣后总月付
    const calculateColumnTotals = () => {
        const totals = [0, 0, 0];
        parts.forEach(part => {
            part.active.forEach((isActive, index) => {
                if (isActive) {
                    const monthlyDiscounted = Math.ceil((part.price * ((100 - discount) / 100)) / 36);
                    totals[index] += monthlyDiscounted;
                }
            });
        });
        return totals;
    }

    const showParts = (activeIndex) => {
        return parts.map(
            (part) => part.active[activeIndex] && (
                <div key={part.bodyPart} className="row">
                    <div className="col-3">{part.bodyPart}</div>
                    <div className="col-2 text-decoration-line-through">${addToTotal(part.price)}</div>
                    <div className="col-2">
                        ${showDiscounted(part.price)}
                    </div>
                    <div className="col-auto">
                        <select className="form-select-sm" aria-label="Default select example" onChange={(e) => handleDiscountChange(part, e)}>
                            <option defaultValue>{discount}</option>
                            <option value="30">30%</option>
                            <option value="40">40%</option>
                            <option value="50">50%</option>
                            <option value="60">60%</option>
                        </select>
                    </div>
                    <div className="col-1 text-end"><button type="button" className="btn-close" aria-label="Close"></button></div>
                </div>)
        )
    }

    const columnTotals = calculateColumnTotals();

    return (
        <div className="">
            <div className="row rows-cols-3">
                <div className="col-4">
                    {showParts(0)}
                </div>
                <div className="col-4">
                    {showParts(1)}
                </div>
                <div className="col-4">
                    {showParts(2)}
                </div>
                {/* 使用计算出的总价格 */}
                <div className="col-4">discounted total for 36 months: {columnTotals[0]}</div>
                <div className="col-4">discounted total for 36 months: {columnTotals[1]}</div>
                <div className="col-4">discounted total for 36 months: {columnTotals[2]}</div>
            </div>
        </div>
    );
}

export default QuoteCalcBot;

备选方案:使用useEffect维护状态

如果一定要将total作为状态维护,可通过useEffect在依赖项变化时更新状态,避免在渲染阶段触发状态更新:

  1. 保留total状态,移除showDiscounted内的setTotal调用
  2. 添加useEffect监听依赖项变化,计算并更新total

核心代码片段:

import { useEffect, useState } from "react";

const QuoteCalcBot = ({ parts, setParts, option, discount }) => {
    const [total, setTotal] = useState([0,0,0]);

    // ... 其他函数保持不变,仅修改showDiscounted和添加useEffect ...

    const showDiscounted = (price) => {
        const temp = Math.ceil((price * ((100 - discount) / 100)) / 36)
        return (
            <div className="col-2">
                {temp}
            </div>
        )
    }

    useEffect(() => {
        const newTotal = [0, 0, 0];
        parts.forEach(part => {
            part.active.forEach((isActive, index) => {
                if (isActive) {
                    const monthlyDiscounted = Math.ceil((part.price * ((100 - discount) / 100)) / 36);
                    newTotal[index] += monthlyDiscounted;
                }
            });
        });
        setTotal(newTotal);
    }, [parts, discount]); // 仅当依赖项变化时执行更新

    // ... 其余代码不变 ...
}

关键说明

  • 派生状态方案更简洁,避免了状态同步的潜在问题,是React官方推荐的做法
  • useEffect方案适合必须将total作为状态的场景,需确保依赖项列表完整,避免遗漏导致数据不同步

内容的提问来源于stack exchange,提问作者Spork

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 02:35:19