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

React跳转商品详情页报reading 'details' undefined错误

问题现象

实现商品卡片点击跳转详情页功能时,从浏览器返回上一页后,点击其他商品卡片跳转对应详情页,会抛出类型错误:
TypeError: Cannot read properties of undefined (reading 'details')
已知details为允许为空的数组类型,代码中已添加product['details']真值判断,预期仅当属性存在时渲染对应内容,但判断逻辑未生效,报错依然触发。

相关问题代码如下:

const [ dataLoad, holdRender ] = useState(false); //hold render until data is ready
  const [ product, setProduct ] = useState({});
  const [ currentProduct, setCurrentProduct ] = useState(0);

  //this useEffect runs once
  useEffect(()=> {
    (async () => {
      //set params id received so whenever it updates the data is updated
      const productId = await props.getProduct(props.match.params.id);
      setCurrentProduct(productId);
    })();
  }, []);

 //this useEffect runs everytime the params id is received/changed
  useEffect(() => {
     //get data from the backend to update UI in time until then hold
    (async () => {
      const data = await props.security.product.data;
      setProduct(data);
    })();
    //if product.details is now available
    if(product['details']){
      //let go of render
      holdRender(true);
    }
    else{
      return null;
    }
    return () => {
      setCurrentProduct(0);
    };
  }, [currentProduct]);

return(
    <div>
        {dataLoad !== false?
        <div>
          <Row>
            <Col>
              <Jumbotron id="productStatsCard">
                <div className="productStats">
                  <h2>{product? product['details_not_verified']: <Loader/>}</h2>
                </div>
              </Jumbotron>
            </Col>
            <Col>
              <Jumbotron id="productStatsCard">
                <div className="productStats">
                  <h2>{product? product['details_verified']: <Loader />}</h2>
                </div>
              </Jumbotron>
            </Col>
          </Row>
          <Row className="mt-5">
            <Col sm={8}>
              <Row>
                <Col>
                  <div className="pdtTableContainer">
                    <div>
                      <Table>
                        <thead>
                          <tr>
                            <th>Category</th>
                            <th>Product Id</th>
                            <th>Product Name</th>
                            <th>Product Type</th>
                          </tr>
                        </thead>
                        <tbody>
                          {dataLoad && product['details']? product['details'].map(detail => (
                            <tr key={product.id}>
                              <td>
                              {product.category}
                              </td>
                              <td>{product.id}</td>
                              <td>
                                  {product.product_title}
                              </td>
                              <td>{product.product_type}</td>
                            </tr>
                          )) : 'No products to display'}
                        </tbody>
                      </Table>
                    </div>
                  </div>
                </Col>
              </Row>
            </Col>
          </Row>
        </div>: <Loader/>}
      </div>
  )
}

const mapStateToProps = (state) => ({
  security: state.security,
});

export default connect(mapStateToProps, { getProduct })(productOverview);
根因分析
  • 状态更新时序错误:React的setState是异步批量更新的,调用setProduct(data)后同步读取product变量,拿到的还是当前闭包里缓存的旧状态,不是刚请求回来的新数据。此时判断product['details']完全是基于旧值判断,会把dataLoad错误设置为true,但新的product数据还没完成赋值,可能为undefined。
  • 状态setter命名错误:const [ dataLoad, holdRender ] = useState(false)中第二个返回值是修改dataLoad的方法,命名为holdRender和实际功能完全相反,极易写反控制逻辑。
  • useEffect逻辑错误:清理函数中setCurrentProduct(0)会在依赖项变化、组件卸载时触发,返回上一页再进入新详情页时,会触发状态更新竞态,旧状态被清空但加载标记未同步重置;同时依赖数组缺失props.match.params.id、props.security.product.data等内部使用的变量,违反React Hook依赖规则,长期捕获闭包旧值。
  • 渲染层防护不足:仅判断了product['details'],未先校验product本身是否存在,当product被重置为undefined时,直接读取product['details']就会触发类型错误;同时未校验details是否为数组,空值场景下直接调用map也会报错。
  • 列表key错误:遍历details时用product.id作为key,所有列表项key重复,会导致React渲染复用异常。
修复方案
  • 修正状态setter命名,切换商品时先重置加载状态和旧商品数据,避免旧数据渲染
  • 调整状态更新时序,等接口数据赋值到product状态后,再设置加载完成标记
  • 增加挂载标记,避免组件卸载后更新状态导致内存泄漏和竞态问题
  • 补全useEffect依赖数组,删除有害的清理函数逻辑
  • 渲染层增加多层空值防护,用可选链、数组类型校验兜底异常场景
  • 修正列表项key,用详情项唯一ID或索引兜底

修复后的完整代码:

const [ dataLoad, setDataLoad ] = useState(false);
const [ product, setProduct ] = useState({});
const [ currentProduct, setCurrentProduct ] = useState(0);

// 路由商品ID变化时同步更新currentProduct
useEffect(()=> {
  (async () => {
    const productId = await props.getProduct(props.match.params.id);
    setCurrentProduct(productId);
  })();
}, [props.match.params.id, props.getProduct]);

// 商品ID变化时拉取对应商品详情
useEffect(() => {
  // 切换商品先重置状态
  setDataLoad(false);
  setProduct({});
  let isMounted = true;

  (async () => {
    const data = await props.security.product.data;
    if(isMounted){
      setProduct(data ?? {});
      setDataLoad(true);
    }
  })();

  return () => {
    isMounted = false;
  };
}, [currentProduct, props.security.product.data]);

return(
  <div>
      {dataLoad && product ?
      <div>
        <Row>
          <Col>
            <Jumbotron id="productStatsCard">
              <div className="productStats">
                <h2>{product?.details_not_verified ?? <Loader/>}</h2>
              </div>
            </Jumbotron>
          </Col>
          <Col>
            <Jumbotron id="productStatsCard">
              <div className="productStats">
                <h2>{product?.details_verified ?? <Loader />}</h2>
              </div>
            </Jumbotron>
          </Col>
        </Row>
        <Row className="mt-5">
          <Col sm={8}>
            <Row>
              <Col>
                <div className="pdtTableContainer">
                  <div>
                    <Table>
                      <thead>
                        <tr>
                          <th>Category</th>
                          <th>Product Id</th>
                          <th>Product Name</th>
                          <th>Product Type</th>
                        </tr>
                      </thead>
                      <tbody>
                        {Array.isArray(product?.details) && product.details.length > 0 
                          ? product.details.map((detail, index) => (
                            <tr key={detail?.id ?? index}>
                              <td>{product.category}</td>
                              <td>{product.id}</td>
                              <td>{product.product_title}</td>
                              <td>{product.product_type}</td>
                            </tr>
                          )) 
                          : 'No products to display'
                        }
                      </tbody>
                    </Table>
                  </div>
                </div>
              </Col>
            </Row>
          </Col>
        </Row>
      </div>: <Loader/>}
    </div>
)
}

const mapStateToProps = (state) => ({
  security: state.security,
});

export default connect(mapStateToProps, { getProduct })(productOverview);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:06:18