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
相关产品推荐
相关产品推荐

