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

React开发NFT市场时为不同NFT设置独立hashtag的问题排查

问题原因

全局只定义了一个tagList数组存储标签,渲染所有NFT卡片时都共用这同一个数组做遍历,没有建立NFT和自身标签的绑定关系,所以只要数组内容变化,所有卡片都会渲染相同的标签。

修复步骤
  • 调整标签存储结构:将单一数组改为对象映射,以NFT的唯一标识(推荐用合约地址+token_id组合,全局唯一不会重复)为键,对应值为该NFT自身的标签数组
  • 拆分编辑态和展示态:弹窗内编辑标签时使用独立的临时状态存储,用户点击保存后再同步到全局标签映射,避免编辑中途取消时错误修改卡片展示内容
  • 点击标签按钮打开弹窗时,自动加载当前操作NFT已有的标签到编辑态
  • 渲染单张NFT卡片时,只读取当前NFT对应键下的标签数组做遍历,不再读取全局统一数组
  • 提交、删除标签操作仅修改当前编辑NFT对应的标签集合,不影响其他NFT的内容
修正后核心代码

状态与逻辑部分

const [visible1, setVisibility1] = useState(false);
const [nftToSend1, setNftToSend1] = useState(null);
const [loading, setLoading] = useState(false);

const [tagItem, setTagItem] = useState('')
// 存储所有NFT的标签映射,key为NFT唯一标识,value为对应标签数组
const [nftTagMap, setNftTagMap] = useState({})
// 弹窗内编辑用的临时标签列表
const [editingTagList, setEditingTagList] = useState([])

// 点击标签按钮打开弹窗逻辑
const handleTagClick = (nft) => {
  setNftToSend1(nft)
  const nftKey = `${nft.token_address}_${nft.token_id}`
  // 加载当前NFT已有标签到编辑态
  setEditingTagList(nftTagMap[nftKey] || [])
  setTagItem('')
  setVisibility1(true)
}

const onKeyPress = e => {
  if (e.target.value.trim().length !== 0 && e.key === 'Enter') {
    submitTagItem()
  }
}

const submitTagItem = () => {
  const updatedTagList = [...editingTagList, tagItem.trim()]
  setEditingTagList(updatedTagList)
  setTagItem('')
}

const deleteTagItem = e => {
  const deleteTarget = e.target.parentElement.firstChild.innerText
  const filteredTagList = editingTagList.filter(tag => tag !== deleteTarget)
  setEditingTagList(filteredTagList)
}

// 保存标签到全局状态
const handleSaveTag = () => {
  if (!nftToSend1) return
  const nftKey = `${nftToSend1.token_address}_${nftToSend1.token_id}`
  setNftTagMap(prev => ({
    ...prev,
    [nftKey]: editingTagList
  }))
  // 如需持久化到Moralis服务端,在此处添加接口调用逻辑即可
  setVisibility1(false)
}

渲染部分

return (
  <> ...
    <div style={styles.NFTs}>
      {NFTBalance &&
        NFTBalance.map((nft) => {
          const currentNftKey = `${nft.token_address}_${nft.token_id}`
          const currentNftTags = nftTagMap[currentNftKey] || []
          return (
            <Card
              hoverable
              actions={[
                <Tooltip title="HashTag">
                  <TagOutlined onClick={() => handleTagClick(nft)} />
                </Tooltip>,
              ]}
              cover={
                <Image
                  preview={false}
                  src={nft?.image || "error"}
                  fallback="data:image/png;base64,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iAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
                  alt=""
                  style={{ height: "240px" }}
                />
              }
              key={currentNftKey}
            >
              <Meta title={nft.name} description={nft.contract_type} />
              {/* 仅渲染当前NFT自身的标签 */}
              {currentNftTags.map((value, idx) => (
                <p key={idx}>#{value}</p>
              ))}
            </Card>
          )
        })}
    </div>
    
    <Modal
      title={`HashTag ${nftToSend1?.name} #${nftToSend1?.token_id}`}
      visible={visible1}
      onCancel={() => setVisibility1(false)}
      footer={[
        <Button onClick={handleSaveTag}>
          Save
        </Button>,
      ]}
    >
      <Spin spinning={loading}>
        <img
          src={`${nftToSend1?.image}`}
          alt=""
          style={{maxWidth: '100%', marginBottom: 16}}
        />
        <Input
          autoFocus
          placeholder="Press Enter to enter a hashtag"
          tabIndex={2}
          onChange={(e) => setTagItem(e.target.value)}
          onKeyPress={onKeyPress}
          value={tagItem}
        />
        {/* 渲染编辑态的临时标签列表 */}
        {editingTagList.map((tag, idx) => {
          return (
            <div key={idx} style={{marginTop: 8}}>
              <span>#{tag}</span>
              <button onClick={deleteTagItem} style={{marginLeft: 8}}>X</button>
            </div>
          )
        })}
      </Spin>
    </Modal>
 ...
额外说明
  • 不要用数组遍历的index作为Card或列表项的key,NFT列表如果做排序、过滤操作,index作为key会导致状态错位,用合约地址+token_id的组合可以保证全局唯一
  • 如果需要标签持久化,在handleSaveTag方法中调用Moralis存储接口,将对应NFT的标签存入数据表即可,初始化加载NFT列表时顺便拉取对应标签填充nftTagMap

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 03:27:31