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;
问题分析与修复方案
核心原因
无限循环的触发链:
SMSForm每次渲染都会重新创建handleKilosData函数,导致NumberOfKilos中useCallback的依赖handleKilosData每次都变化。handleKilosData变化会让calculateKilosData重新生成,触发useEffect执行。calculateKilosData调用handleKilosData更新SMSForm的状态,导致SMSForm重新渲染,再次创建新的handleKilosData,形成循环。- 额外问题:传递给父组件的
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
相关产品推荐
相关产品推荐

