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

React组件传值引发Maximum update depth exceeded问题求助

解决React组件间数据传递的无限循环问题

我是React新手,想要实现NumberOfKilos组件向SMSForm组件传递数据,在React Bootstrap的Summary Card中展示数据,但浏览器控制台出现“Maximum update depth exceeded”警告,尝试过useCallback和useRef方法仍未解决,附上相关组件代码。

浏览器控制台错误信息

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

SMSForm组件(子组件)

import NumberOfKilos from "../Components/NumberOfKilosCard";
import { Row, Col, Container, Form, Card } from 'react-bootstrap';
import { useState } from "react";

function SMSForm() {
  const [kilosData, setKilosData] = useState({
    numKilosSale: 0,
    numKilosCredit: 0,
    totalKilos: 0,
    cashSales: 0,
    creditSales: 0,
    totalSales: 0,
  });

  // Function to update kilos data
  const handleKilosData = (data) => {
    setKilosData(data);
  };

  return (
    <Container fluid>
      <Row>
        <Col>
          <NumberOfKilos handleKilosData={handleKilosData} />
        </Col>


        <Col md={3}>
          <Form>
            <Card>
              <Card.Header className='bg-dark text-light ' as="h4">
                Summary
              </Card.Header>

              <Row className='mx-3 mt-3'>
                <Col>
                  <Form.Label>Number of Kilos (Sale):</Form.Label>
                </Col>
                <Col xs={4}>
                  {kilosData.numKilosSale}
                </Col>
              </Row>

              <Row className='mx-3 mt-3'>
                <Col>
                  <Form.Label>Number of Kilos (Credit):</Form.Label>
                </Col>
                <Col xs={4}>
                  {kilosData.numKilosCredit}
                </Col>
              </Row>

              <Row className='mx-3 mt-3'>
                <Col>
                  <Form.Label>Total Kilos:</Form.Label>
                </Col>
                <Col xs={4}>
                  {kilosData.totalKilos}
                </Col>
              </Row>

              <Row className='mx-3 mt-3'>
                <Col>
                  <Form.Label>Cash Sales:</Form.Label>
                </Col>
                <Col xs={4}>
                  {kilosData.cashSales}
                </Col>
              </Row>

              <Row className='mx-3 mt-3'>
                <Col>
                  <Form.Label>Credit Sales:</Form.Label>
                </Col>
                <Col xs={4}>
                  {kilosData.creditSales}
                </Col>
              </Row>

              <Row className='mx-3 mt-3'>
                <Col>
                  <Form.Label>Total Sales:</Form.Label>
                </Col>
                <Col xs={4}>
                  {kilosData.totalSales}
                </Col>
              </Row>


            </Card>
          </Form>
        </Col>
      </Row>
    </Container >
  );
}

export default SMSForm;

NumberOfKilos组件(父组件,负责传递数据)

import React, { useState, useEffect, useCallback } from 'react';
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
import { FiTrash } from 'react-icons/fi';

function NumberOfKilos({ handleKilosData }) {
  const [numKilosRow, setNumKilosRow] = useState([
    { product: '', kilosSold: '', productSales: '', paymentStat: '' },
  ]);

  const handleAddNumKilosRow = () => {
    setNumKilosRow([...numKilosRow, { product: '', kilosSold: '', productSales: '', paymentStat: '' }]);
  };

  const handleDeleteNumKilosRow = (index) => {
    if (numKilosRow.length > 1) {
      const updatedKilosRows = [...numKilosRow];
      updatedKilosRows.splice(index, 1);
      setNumKilosRow(updatedKilosRows);
    }
  };

  const handleNumKilosChange = (value, index, field) => {
    const updatedKilosRows = [...numKilosRow];
    updatedKilosRows[index][field] = value;
    setNumKilosRow(updatedKilosRows);
  };

  const calculateKilosData = useCallback(() => {
    let saleKilosSum = 0;
    let creditKilosSum = 0;
    let cashSalesSum = 0;
    let creditSalesSum = 0;

    numKilosRow.forEach((row) => {
      if (row.paymentStat === 'Sale') {
        if (row.kilosSold) saleKilosSum += parseFloat(row.kilosSold);
        if (row.productSales) cashSalesSum += parseFloat(row.productSales);
      } else if (row.paymentStat === 'Credit') {
        if (row.kilosSold) creditKilosSum += parseFloat(row.kilosSold);
        if (row.productSales) creditSalesSum += parseFloat(row.productSales);
      }
    });

    const kilosDataObject = {
      numKilosRow,
      numKilosSale: saleKilosSum,
      numKilosCredit: creditKilosSum,
      cashSales: cashSalesSum,
      creditSales: creditSalesSum,
      totalKilos: saleKilosSum + creditKilosSum,
      totalSales: cashSalesSum + creditSalesSum,
    };

    handleKilosData(kilosDataObject);
  }, [numKilosRow, handleKilosData]);

  useEffect(() => {
    calculateKilosData();
  }, [calculateKilosData]);

  return (
    <Container>
      <Form>
        <Row className='mb-3'>
          <Col>
            <Card style={{ height: '250px', overflow: 'auto' }}>
              <Card.Header as='h4' className='bg-dark text-light' style={{ position: 'sticky', top: '0', zIndex: '1' }}>
                Number of Kilos
              </Card.Header>

              <Row className='mx-3 mt-3'>
                <Col>
                  <Form.Label>Product</Form.Label>
                </Col>

                <Col>
                  <Form.Label>Payment Status</Form.Label>
                </Col>

                <Col>
                  <Form.Label>Kilos Sold</Form.Label>
                </Col>

                <Col>
                  <Form.Label>Product Sale</Form.Label>
                </Col>

                <Col xs={1}></Col>
              </Row>

              {numKilosRow.map((row, index) => (
                <Row className='mx-3' key={index}>
                  <Col>
                    <Form.Group className='mb-3' controlId={`productSelection${index}`}>
                      <Form.Select value={row.product} onChange={(e) => handleNumKilosChange(e.target.value, index, 'product')}>
                        <option>Select a product</option>
                        <option value='Fish'>Fish</option>
                        <option value='Squid'>Squid</option>
                        <option value='Octopus'>Octopus</option>
                        <option value='Cuttlefish'>Cuttlefish</option>
                        <option value='Cogita'>Cogita</option>
                      </Form.Select>
                    </Form.Group>
                  </Col>

                  <Col>
                    <Form.Select value={row.paymentStat} onChange={(e) => handleNumKilosChange(e.target.value, index, 'paymentStat')}>
                      <option>Select Status</option>
                      <option value='Sale'>Sale</option>
                      <option value='Credit'>Credit</option>
                    </Form.Select>
                  </Col>

                  <Col>
                    <Form.Group className='mb-3' controlId={`kilosSold${index}`}>
                      <Form.Control type='number' placeholder='Enter kilos sold' value={row.kilosSold} onChange={(e) => handleNumKilosChange(e.target.value, index, 'kilosSold')} />
                    </Form.Group>
                  </Col>

                  <Col>
                    <Form.Group className='mb-3' controlId={`productSales${index}`}>
                      <Form.Control type='number' placeholder='Enter sale' value={row.productSales} onChange={(e) => handleNumKilosChange(e.target.value, index, 'productSales')} />
                    </Form.Group>
                  </Col>

                  <Col xs={1}>
                    {numKilosRow.length > 1 && (
                      <Button variant='outline-danger' onClick={() => handleDeleteNumKilosRow(index)}>
                        <FiTrash />
                      </Button>
                    )}
                  </Col>
                </Row>
              ))}

              <Row className='mx-3 mb-3'>
                <Col>
                  <Button variant='primary' onClick={handleAddNumKilosRow}>
                    + Add Another Row
                  </Button>
                </Col>
              </Row>
            </Card>
          </Col>
        </Row>
      </Form>
    </Container>
  );
}

export default NumberOfKilos;

问题分析与修复方案

核心原因

无限循环的触发链:

  1. SMSForm每次渲染都会重新创建handleKilosData函数,导致NumberOfKilos中useCallback的依赖handleKilosData每次都变化。
  2. handleKilosData变化会让calculateKilosData重新生成,触发useEffect执行。
  3. calculateKilosData调用handleKilosData更新SMSForm的状态,导致SMSForm重新渲染,再次创建新的handleKilosData,形成循环。
  4. 额外问题:传递给父组件的kilosDataObject包含numKilosRow数组(引用类型),即使数据没变化,每次传递的都是新引用,加剧了循环风险。

修复步骤

1. 缓存SMSForm中的handleKilosData

用useCallback缓存该方法,利用React保证setKilosData引用稳定的特性,避免每次渲染重新创建:

import { useState, useCallback } from "react";

function SMSForm() {
  const [kilosData, setKilosData] = useState({
    numKilosSale: 0,
    numKilosCredit: 0,
    totalKilos: 0,
    cashSales: 0,
    creditSales: 0,
    totalSales: 0,
  });

  // 缓存方法,依赖仅保留setKilosData
  const handleKilosData = useCallback((data) => {
    setKilosData(data);
  }, [setKilosData]);

  // 剩余代码不变
}

2. 移除不必要的引用传递

在NumberOfKilos的calculateKilosData中,不要把numKilosRow放进传递给父组件的对象里,父组件不需要这个数据:

const calculateKilosData = useCallback(() => {
  let saleKilosSum = 0;
  let creditKilosSum = 0;
  let cashSalesSum = 0;
  let creditSalesSum = 0;

  numKilosRow.forEach((row) => {
    if (row.paymentStat === 'Sale') {
      if (row.kilosSold) saleKilosSum += parseFloat(row.kilosSold);
      if (row.productSales) cashSalesSum += parseFloat(row.productSales);
    } else if (row.paymentStat === 'Credit') {
      if (row.kilosSold) creditKilosSum += parseFloat(row.kilosSold);
      if (row.productSales) creditSalesSum += parseFloat(row.productSales);
    }
  });

  const kilosDataObject = {
    // 移除numKilosRow
    numKilosSale: saleKilosSum,
    numKilosCredit: creditKilosSum,
    cashSales: cashSalesSum,
    creditSales: creditSalesSum,
    totalKilos: saleKilosSum + creditKilosSum,
    totalSales: cashSalesSum + creditSalesSum,
  };

  handleKilosData(kilosDataObject);
}, [numKilosRow, handleKilosData]);

3. 可选:简化计算触发逻辑

可以直接让useEffect依赖numKilosRow,去掉中间的useCallback,减少不必要的函数生成:

// 移除useCallback包裹,改为普通函数
const calculateKilosData = () => {
  // 计算逻辑不变
};

useEffect(() => {
  calculateKilosData();
}, [numKilosRow]); // 仅当numKilosRow变化时执行计算

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 04:07:34