React报错求助:渲染Cart时更新Connect(Cart)的问题排查
问题排查:Cannot update a component (Connect(Cart)) while rendering a different component (Cart)
问题描述
出现警告:Cannot update a component (Connect(Cart)) while rendering a different component (Cart),提示需定位Cart组件内错误的setState()调用,无法确定问题出在Cart.js还是使用Redux Thunk的cartActions.js中。
相关代码
Cart.js
import { Component, Fragment } from "react"; import AppNavbar from './AppNavbar'; import { Card, CardText, CardBody, CardTitle, CardSubtitle, Button, Alert, Container } from 'reactstrap'; import PropTypes from 'prop-types'; import { connect }from 'react-redux'; import { getCart, deleteFromCart } from '../actions/cartActions'; import Checkout from "./Checkout"; import { checkout } from '../actions/orderActions'; class Cart extends Component { constructor(props) { super(props); this.state = { loaded: false } } static propTypes = { getCart: PropTypes.func.isRequired, isAuthenticated: PropTypes.bool, addToCart: PropTypes.func.isRequired, deleteFromCart: PropTypes.func.isRequired, user: PropTypes.object.isRequired, cart: PropTypes.object.isRequired, checkout: PropTypes.func.isRequired } getCartItems = (id) => { this.props.getCart(id).then(() => { this.setState({ loaded: true }); }); } onDeleteFromCart = (id, itemId) => { this.props.deleteFromCart(id, itemId); } componentDidMount() { const { isAuthenticated, cart, user } = this.props; if (isAuthenticated && !cart.loading && !this.state.loaded) { this.getCartItems(user._id); } } componentDidUpdate(prevProps) { const { isAuthenticated, cart, user } = this.props; if (isAuthenticated !== prevProps.isAuthenticated || cart.loading !== prevProps.cart.loading) { if (isAuthenticated && !cart.loading && !this.state.loaded) { this.getCartItems(user._id); } } } render() { const user = this.props.user; return ( <div> <AppNavbar /> {this.props.isAuthenticated ? <Fragment> {this.props.cart.cart ? null : <Alert color="info" className="text-center">你的购物车是空的!</Alert> } </Fragment> : <Alert color="danger" className="text-center">请登录后查看!</Alert> } {this.props.isAuthenticated && !this.props.cart.loading && this.state.loaded && this.props.cart.cart ? <Container> <div className="row"> {this.props.cart.cart.items.map(item => ( <div className="col-md-4" key={item.productId}> <Card> <CardBody> <CardTitle tag="h5">{item.name}</CardTitle> <CardSubtitle tag="h6">USD {item.price}</CardSubtitle> <CardText>数量 - {item.quantity}</CardText> <Button color="danger" onClick={() => this.onDeleteFromCart(user._id, item.productId)}>删除</Button> </CardBody> </Card> <br /> </div> ))} <div className="col-md-12"> <Card> <CardBody> <CardTitle tag="h5">总金额 = USD {this.props.cart.cart.bill}</CardTitle> <Checkout user={user._id} amount={this.props.cart.cart.bill} checkout={this.props.checkout} /> </CardBody> </Card> </div> </div> </Container> : null} </div> ); } } const mapStateToProps = state => ({ cart: state.cart, isAuthenticated: state.auth.isAuthenticated, user: state.auth.user }) export default connect(mapStateToProps, {getCart, deleteFromCart, checkout})(Cart);
cartActions.js
import axios from "../http-common"; import { GET_CART, ADD_TO_CART, DELETE_FROM_CART, CART_LOADING } from "./types"; import { returnErrors } from './errorActions'; export const getCart = (id) => (dispatch) => { dispatch(setCartLoading()); return axios.get(`/api/cart/${id}`) .then(res => { dispatch({ type: GET_CART, payload: res.data }); }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); }); } export const addToCart = (id, productId, quantity) => (dispatch) => { axios.post(`/api/cart/${id}`, {productId, quantity}) .then(res => { dispatch({ type: ADD_TO_CART, payload: res.data }); }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); }); } export const deleteFromCart = (userId, itemId) => (dispatch) => { axios.delete(`/api/cart/${userId}/${itemId}`) .then(res => { dispatch({ type: DELETE_FROM_CART, payload: res.data }); }) .catch(err => { dispatch(returnErrors(err.response.data, err.response.status)); }); } export const setCartLoading = () => { return { type: CART_LOADING } }
问题原因及修复方案
核心问题
警告的根源是在render方法中调用了会触发状态更新(包括Redux state和组件自身state)的函数:
- 原代码在
render里直接调用getCartItems,这个函数内部调用Redux的getCart(会dispatch action更新Redux state,导致Connect(Cart)组件重新渲染),同时调用setState更新组件自身state。 - React的
render是纯函数,不应包含任何副作用(比如状态更新、数据请求),否则会导致渲染过程中触发组件更新,引发冲突警告。
修复步骤
- 将数据获取逻辑移到生命周期方法:
- 把
getCartItems调用移到componentDidMount(组件挂载时)和componentDidUpdate(组件props/state变化时),这两个生命周期是处理副作用的正确时机。
- 把
- 修正异步逻辑:
- 修改
getCart让它返回axios的Promise,这样可以在请求完成后再设置loaded状态,原代码的await无效,因为getCart返回的是Thunk函数而非Promise。
- 修改
- 优化事件绑定:
- 把
bind绑定事件改成箭头函数形式,避免每次render创建新的绑定函数,提升性能。
- 把
- 添加列表key属性:
- 在
map渲染的列表项中添加key属性,符合React列表渲染规范。
- 在
内容的提问来源于stack exchange,提问作者Zonaed Hasan
相关产品推荐
相关产品推荐

