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

React Hooks更新状态未触发重渲染:NFT画廊搜索功能异常

问题描述

我正在开发一个基于Alchemy SDK获取NFT集合并展示的UI,通过getServerSideProps传递初始NFT集合给Home组件,再将父组件Home的nfts状态传递给Gallery子组件渲染Card。目前实现搜索合约地址更新NFT集合功能时,父组件状态已更新并传递给Gallery,但Gallery未触发重渲染。虽使用useEffect但子组件状态未同步,直接使用props可显示但无法实现后续过滤有效图片NFT的需求,现寻求解决方法。

原父组件代码

const Home = ({results}: {results: object}) => {
  const [nfts, setNfts] = useState(results.nfts);
  const [userInput, setUserInput] = useState('');

  useEffect(() => {
    setNfts(results.nfts);
  }, [])

  const handleSubmit = async(e) => {
    e.preventDefault();
    const newCollection = await findNftsByContractAddress(userInput);
    const newResults = JSON.parse(JSON.stringify(newCollection))

    setNfts(newResults.nfts);
  }

  return (
    <div>
      <Head>
        <title>Shibuya Take Home</title>
      </Head>

      <NavBar />
      <form action="submit" >
        <input type="text" className="search-bar" onChange={e => setUserInput(e.target.value)} />
        <input type="submit" onClick={handleSubmit}/>
      </form>
      {/* Search Bar Component Goes Here */}
      {/* Title text */}
      <Gallery key={nfts} nfts={nfts}/>
    </div>
  );
};

export default Home;

export const getServerSideProps = async() => {
  // function is going to need to take in user input and replace the bored ape yacht club with user address
  const boredApeYachtClubRaw = await findNftsByContractAddress("0xed5af388653567af2f388e6224dc7c4b3241c544");
  const results = JSON.parse(JSON.stringify(boredApeYachtClubRaw))

  return {props: {
    results
  }}
}

原子组件Gallery代码

export default function Gallery({nfts}: {nfts: object[]}) {
    const [title, setTitle] = useState("");
    const [description, setDescription] = useState("");
    const [collection, setCollection] = useState([]);

    useEffect(() => {
        setTitle(nfts[0].contract.name);
        setDescription(nfts[0].description);
        setCollection(nfts);
    }, [title, description, collection])

    return(
        <div className="gallery-container">
            <h1 className="gallery-title">{nfts[0].contract.name}</h1>
            <p className="gallery-description">{nfts[0].description}</p>
            <div className="cards-container">
                {nfts.map(nft => {
                    return (
                        <Card image={nft.media[0].gateway} tokenId={nft.tokenId}/>
                    )
                })}
            </div>
        </div>
    )
}

NFT对象结构

0: 
contract: 
{address: '0xed5af388653567af2f388e6224dc7c4b3241c544', name: 'Azuki', symbol: 'AZUKI', totalSupply: '10000', tokenType: 'ERC721'}
description
: 
""
media
: 
[{…}]
rawMetadata
: 
{name: 'Azuki #0', image: 'https://ikzttp.mypinata.cloud/ipfs/QmYDvPAXtiJg7s8JdRBSLWdgSphQdac8j1YuQNNxcGE1hg/0.png', attributes: Array(7)}
timeLastUpdated
: 
"2022-10-30T14:58:51.119Z"
title
: 
"Azuki #0"
tokenId
: 
"0"
tokenType
: 
"ERC721"
tokenUri
: 
{raw: 'https://ikzttp.mypinata.cloud/ipfs/QmQFkLSQysj94s5GvTHPyzTxrawwtjgiiYS2TBLgrvw8CW/0', gateway: 'https://ipfs.io/ipfs/Qm...'}

解决方案

1. 修复子组件useEffect依赖问题

子组件的useEffect依赖设置错误,当前依赖自身状态title、description、collection,父组件传递新nfts时不会触发该钩子。需将依赖改为[nfts],确保父组件更新nfts后,子组件同步更新自身状态。

2. 移除父组件中无效的key属性

给Gallery设置key={nfts}无效,数组是引用类型,React浅比较会认为引用未变化,无法触发重渲染。直接移除该key,React会通过props变化自动触发子组件重渲染。

3. 实现过滤有效图片NFT的逻辑

在子组件useEffect中,过滤出包含有效图片的NFT(即media数组存在且第一个元素有gateway字段的NFT),将结果存入collection状态,渲染时使用collection而非原始nfts。

4. 优化父组件初始状态设置

父组件中用于初始化nfts的useEffect多余,直接在useState中初始化即可,无需额外钩子。


修改后的父组件代码

const Home = ({results}: {results: object}) => {
  // 直接用results.nfts初始化状态,无需额外useEffect
  const [nfts, setNfts] = useState(results.nfts);
  const [userInput, setUserInput] = useState('');

  const handleSubmit = async(e) => {
    e.preventDefault();
    const newCollection = await findNftsByContractAddress(userInput);
    const newResults = JSON.parse(JSON.stringify(newCollection))

    setNfts(newResults.nfts);
  }

  return (
    <div>
      <Head>
        <title>Shibuya Take Home</title>
      </Head>

      <NavBar />
      <form action="submit" >
        <input type="text" className="search-bar" onChange={e => setUserInput(e.target.value)} />
        <input type="submit" onClick={handleSubmit}/>
      </form>
      {/* 移除无效的key属性 */}
      <Gallery nfts={nfts}/>
    </div>
  );
};

export default Home;

export const getServerSideProps = async() => {
  const boredApeYachtClubRaw = await findNftsByContractAddress("0xed5af388653567af2f388e6224dc7c4b3241c544");
  const results = JSON.parse(JSON.stringify(boredApeYachtClubRaw))

  return {props: {
    results
  }}
}

修改后的子组件Gallery代码

export default function Gallery({nfts}: {nfts: object[]}) {
    const [title, setTitle] = useState("");
    const [description, setDescription] = useState("");
    const [collection, setCollection] = useState([]);

    useEffect(() => {
        // 确保nfts数组不为空再访问第一个元素
        if (nfts.length > 0) {
            setTitle(nfts[0].contract.name);
            setDescription(nfts[0].description);
        }
        // 过滤出包含有效图片的NFT
        const filteredNfts = nfts.filter(nft => {
            return nft.media && nft.media.length > 0 && nft.media[0].gateway;
        });
        setCollection(filteredNfts);
    }, [nfts]) // 依赖改为nfts,父组件更新时触发

    return(
        <div className="gallery-container">
            {/* 处理nfts为空的情况,避免报错 */}
            <h1 className="gallery-title">{title || 'NFT Collection'}</h1>
            <p className="gallery-description">{description || 'No description available'}</p>
            <div className="cards-container">
                {/* 渲染过滤后的collection */}
                {collection.map(nft => {
                    return (
                        <Card key={nft.tokenId} image={nft.media[0].gateway} tokenId={nft.tokenId}/>
                    )
                })}
            </div>
        </div>
    )
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:55:36