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

MERN栈直播拍卖应用MediaCard组件TypeError问题求助

直播拍卖应用MediaCard组件TypeError问题排查与解决

报错信息

Uncaught TypeError: Cannot read properties of undefined (reading 'image')
    at MediaCard (Card.js:53:1)
    at renderWithHooks (react-dom.development.js:16305:1)
    at mountIndeterminateComponent (react-dom.development.js:20074:1)
    at beginWork (react-dom.development.js:21587:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
    at invokeGuardedCallback (react-dom.development.js:4277:1)
    at beginWork$1 (react-dom.development.js:27451:1)
    at performUnitOfWork (react-dom.development.js:26557:1)
    at workLoopSync (react-dom.development.js:26466:1)

MediaCard组件代码

import * as React from "react";
import { connect } from "react-redux";
import Card from "@mui/material/Card";
import { Link, useNavigate } from "react-router-dom";
// Actions
import {
  loadAdDetails,
  loadAdImage,
  setImageLoadingStatus,
} from "../../actions/ad";
// MUI Components
import { CardActionArea } from "@mui/material";
import CardContent from "@mui/material/CardContent";
import CardMedia from "@mui/material/CardMedia";
import Typography from "@mui/material/Typography";
// Files
import imagePlaceholder from "../../images/no-image-icon.png";
import { secondsToHmsShort } from "../../utils/secondToHms";

function MediaCard(props) {
  const navigate = useNavigate();

  const handleCardClick = (e) => {
    navigate(`/ads/${props.ad._id}`);
  };

  // Auction status based on the ad-details
  const updateAuctionStatus = (ad) => {
    if (ad.sold) {
      return "Sold";
    } else if (ad.auctionEnded) {
      return "Ended, not-sold";
    } else if (!ad.auctionStarted) {
      return "Upcoming";
    } else {
      return "Ongoing";
    }
  };

  return (
    <a
      onClick={(e) => {
        handleCardClick(e);
      }}
      style={{ textDecoration: "none" }}
    >
      <Card style={props.cardStyle}>
        <CardActionArea>
          {!props.dashCard && (
            <CardMedia
              component="img"
              height="180"
              src={props.ad.image ? props.ad.image : imagePlaceholder}
              alt="green iguana"
            />
          )}
          <CardContent>
            <Typography gutterBottom variant="h6" component="div">
              {props.ad.productName}
            </Typography>
            <Typography variant="body2" color="text.secondary">
              Price: $ {props.ad.currentPrice.$numberDecimal}
            </Typography>
            <Typography variant="body2" color="text.secondary">
              Remaining: {secondsToHmsShort(props.ad.timer)}
            </Typography>
            <Typography variant="body2" color="text.secondary">
              Status: {updateAuctionStatus(props.ad)}
            </Typography>
          </CardContent>
        </CardActionArea>
      </Card>
    </a>
  );
}

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

export default connect(mapStateToProps, {
  loadAdDetails,
  loadAdImage,
  setImageLoadingStatus,
})(MediaCard);

页面核心代码片段

return (
    <div className="ad__page">
      {props.loading ? (
        <LoadingDisplay />
      ) : (
        <>
          <Alert />
          {!props.adDetails.owner ? (
            <LoadingDisplay />
          ) : (
            <Box sx={boxStyle}>
              <Paper sx={paperStyle}>
                <Typography variant="h4">
                  {props.adDetails.productName}
                </Typography>

                <Box sx={adArea}>
                  <Box sx={imageContainer}>
                    {!props.imageLoading && (
                      <img
                        src={
                          props.adDetails.image
                            ? props.adImage
                            : imagePlaceholder
                        }
                        alt={props.adDetails.productName}
                        style={imageStyle}
                      />
                    )}
                  </Box>

                  <Box sx={descriptionArea}>
                    <Typography variant="h6">Description</Typography>
                    <Typography variant="body2">
                      {props.adDetails.description}
                    </Typography>
                    <Divider variant="middle" sx={{ margin: ".5rem" }} />

                    <Typography variant="h6">Info</Typography>
                    <Typography variant="body1">
                      Posted on: {getUTCDate(props.adDetails.createdAt)}
                    </Typography>
                    <Typography variant="body1">
                      Seller: {props.adDetails.owner.username}
                    </Typography>
                    <Typography variant="body1">
                      Base price: {props.adDetails.basePrice.$numberDecimal}
                    </Typography>
                    <Divider variant="middle" sx={{ margin: ".5rem" }} />

                    <Typography variant="h6">Auction</Typography>
                    <Typography variant="body1">
                      Status: {auctionStatus()}
                    </Typography>
                    <Typography variant="body1">
                      Bids: {props.adDetails.bids.length}
                    </Typography>
                    <Typography variant="body1">
                      Time remaining: {getTimeRemaining()}
                    </Typography>
                    <Typography variant="body1">
                      Current price: $
                      {props.adDetails.currentPrice.$numberDecimal}
                    </Typography>
                    <Typography variant="body1">
                      Current bidder: 
                      {props.highestBid && props.highestBid.user.username}
                    </Typography>
                    <Divider variant="middle" sx={{ margin: ".5rem" }} />

                    {!ownerAd && (
                      <Box sx={bidContainer}>
                        <TextField
                          label="$"
                          id="bid-price"
                          size="small"
                          onChange={(e) => {
                            handleBidPriceChange(e);
                          }}
                        />
                        <Box sx={{ height: "auto" }}>
                          <Button
                            variant="contained"
                            disabled={bidButton}
                            onClick={(e) => handleSubmitBid(e)}
                            sx={bidButtonStyle}
                          >
                            Place bid
                          </Button>
                        </Box>
                      </Box>
                    )}
                    {ownerAd && (
                      <Box sx={bidContainer}>
                        <Box sx={{ height: "auto" }}>
                          <Button
                            variant="contained"
                            disabled={!startButton}
                            onClick={(e) => handleStartAuction(e)}
                            sx={bidButton}
                          >
                            Start Auction
                          </Button>
                        </Box>
                      </Box>
                    )}
                  </Box>
                </Box>
              </Paper>
            </Box>
          )}
        </>
      )}
    </div>
  );

问题原因

  1. props来源不匹配:MediaCard组件通过mapStateToProps获取了adDetails,但组件内部一直使用props.ad,如果父组件没有传递ad属性,或者props.ad初始为undefined,就会触发报错。
  2. 缺少加载状态校验:MediaCard组件没有判断props.ad是否存在就直接渲染,在数据未从后端加载完成时,props.ad还未被赋值,导致读取image等属性失败。

解决方法

方法一:修复props映射或使用正确的props

如果广告数据来自Redux的adDetails,修改mapStateToProps将其映射为ad,确保组件内部使用的props一致:

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

或者直接在组件内将props.ad替换为props.adDetails,同时更新所有相关引用(比如props.adDetails._id、props.adDetails.image等)。

方法二:添加加载状态校验

在MediaCard组件渲染前,先判断props.ad是否存在,不存在则返回加载组件或空内容:

function MediaCard(props) {
  const navigate = useNavigate();

  // 新增:判断ad是否存在,不存在则显示加载
  if (!props.ad) {
    return <LoadingDisplay />;
  }

  const handleCardClick = (e) => {
    navigate(`/ads/${props.ad._id}`);
  };

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

方法三:使用可选链操作符做防御性访问

在读取props.ad的属性时,添加可选链?.避免直接读取undefined的属性,这是临时规避报错的方案,建议配合加载校验一起使用:

// 修改CardMedia的src
src={props.ad?.image ? props.ad.image : imagePlaceholder}
// 修改其余属性引用
{props.ad?.productName}
Price: $ {props.ad?.currentPrice?.$numberDecimal}
Remaining: {secondsToHmsShort(props.ad?.timer)}
Status: {updateAuctionStatus(props.ad)}

方法四:确保父组件在数据加载完成后再渲染MediaCard

如果ad是父组件传递的props,父组件需要在adDetails加载完成后再渲染MediaCard,比如:

// 父组件中渲染MediaCard的位置
{props.adDetails && <MediaCard ad={props.adDetails} />}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 06:42:04